diff --git a/.gitignore b/.gitignore index 38a7c293e..e1aa4e411 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ **/bin /.gradle **/.gradle +/.gradle-user +/.gradle-user-home /minecraft */minecraft /out @@ -12,6 +14,9 @@ */out /run /classes +**/remappedSrc +*_unpacked +unpacked_*.txt # IDE nonsense that could go in source control but really shouldn't .classpath @@ -31,4 +36,4 @@ private.properties # Files from bad operating systems :^) Thumbs.db .DS_Store -/.architectury-transformer/debug.log \ No newline at end of file +/.architectury-transformer/debug.log diff --git a/api/build.gradle b/api/build.gradle index b0eeca7df..c3290acf8 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,15 +1,17 @@ import net.fabricmc.loom.task.RemapJarTask -archivesBaseName = rootProject.name + "-" + project.name +base { + archivesName = rootProject.name + "-" + project.name +} loom { accessWidenerPath = gradle.rootProject.project("fabric").file("src/main/resources/roughlyenoughitems.accessWidener") } dependencies { - modCompileOnly("net.fabricmc:fabric-loader:${project.fabricloader_version}") - modApi("me.shedaniel.cloth:cloth-config:${cloth_config_version}") - modApi("dev.architectury:architectury:${architectury_version}") + compileOnly("net.fabricmc:fabric-loader:${project.fabricloader_version}") + api("me.shedaniel.cloth:cloth-config:${cloth_config_version}") + api("dev.architectury:architectury:${architectury_version}") } architectury { diff --git a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Widget.java b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Widget.java index 8a3c01f77..ecb15dc6d 100644 --- a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Widget.java +++ b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Widget.java @@ -33,6 +33,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Renderable; import org.jetbrains.annotations.ApiStatus; import org.joml.Matrix3x2f; @@ -105,6 +106,14 @@ public final boolean isMouseOver(double mouseX, double mouseY) { public void render(GuiGraphics graphics, Rectangle bounds, int mouseX, int mouseY, float delta) { render(graphics, mouseX, mouseY, delta); } + + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + } + + @Override + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { + render(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), mouseX, mouseY, delta); + } @ApiStatus.Experimental public double getZRenderingPriority() { diff --git a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/WidgetWithBounds.java b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/WidgetWithBounds.java index 4d9c9e279..24b4ed06b 100644 --- a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/WidgetWithBounds.java +++ b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/WidgetWithBounds.java @@ -43,6 +43,11 @@ public void render(GuiGraphics graphics, Rectangle bounds, int mouseX, int mouse render(graphics, mouseX, mouseY, delta); getBounds().setBounds(clone); } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + render(graphics, getBounds(), mouseX, mouseY, delta); + } @ApiStatus.Experimental public final WidgetWithBounds withPadding(int padding) { diff --git a/api/src/main/java/me/shedaniel/rei/api/common/transfer/ItemRecipeFinder.java b/api/src/main/java/me/shedaniel/rei/api/common/transfer/ItemRecipeFinder.java index 59480aaba..de65401d7 100644 --- a/api/src/main/java/me/shedaniel/rei/api/common/transfer/ItemRecipeFinder.java +++ b/api/src/main/java/me/shedaniel/rei/api/common/transfer/ItemRecipeFinder.java @@ -92,7 +92,7 @@ public int countRecipeCrafts(List> list, int maxCrafts, @Nullabl } private ItemKey ofKey(ItemStack itemStack) { - return keys.intern(new ItemKey(itemStack.getItemHolder(), itemStack.getComponentsPatch())); + return keys.intern(new ItemKey(itemStack.typeHolder(), itemStack.getComponentsPatch())); } private Ingredient ofKeys(int index, List itemStack) { diff --git a/api/src/main/java/me/shedaniel/rei/api/common/util/EntryIngredients.java b/api/src/main/java/me/shedaniel/rei/api/common/util/EntryIngredients.java index 68a47b5e5..242dd53bb 100644 --- a/api/src/main/java/me/shedaniel/rei/api/common/util/EntryIngredients.java +++ b/api/src/main/java/me/shedaniel/rei/api/common/util/EntryIngredients.java @@ -33,6 +33,8 @@ import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; import me.shedaniel.rei.impl.Internals; import me.shedaniel.rei.impl.common.InternalLogger; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.core.Holder; import net.minecraft.core.HolderGetter; import net.minecraft.core.HolderSet; @@ -41,6 +43,7 @@ import net.minecraft.util.context.ContextMap; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.display.SlotDisplay; import net.minecraft.world.item.crafting.display.SlotDisplayContext; @@ -68,6 +71,10 @@ public static EntryIngredient of(ItemLike stack, int amount) { public static EntryIngredient of(ItemStack stack) { return EntryIngredient.of(EntryStacks.of(stack)); } + + public static EntryIngredient of(ItemStackTemplate stack) { + return EntryIngredient.of(EntryStacks.of(stack.create())); + } public static EntryIngredient of(Fluid fluid) { return EntryIngredient.of(EntryStacks.of(fluid)); @@ -229,21 +236,34 @@ public static EntryIngredient ofSlotDisplay(SlotDisplay slot) { } yield builder.build(); } - // TODO: Bad idea - case SlotDisplay.AnyFuel s -> EntryIngredient.empty(); - default -> { - RegistryAccess access = Internals.getRegistryAccess(); - try { - yield ofItemStacks(slot.resolveForStacks(new ContextMap.Builder() - .withParameter(SlotDisplayContext.REGISTRIES, access) - .create(SlotDisplayContext.CONTEXT))); - } catch (Exception e) { - InternalLogger.getInstance().warn("Failed to resolve slot display: " + slot, e); - yield EntryIngredient.empty(); - } - } + case SlotDisplay.AnyFuel s -> resolveSlotDisplay(s); + default -> resolveSlotDisplay(slot); }; } + + public static ContextMap slotDisplayContext() { + Minecraft client = Minecraft.getInstance(); + if (client.level != null) { + return SlotDisplayContext.fromLevel(client.level); + } + + ContextMap.Builder builder = new ContextMap.Builder() + .withParameter(SlotDisplayContext.REGISTRIES, Internals.getRegistryAccess()); + ClientPacketListener connection = client.getConnection(); + if (connection != null) { + builder.withParameter(SlotDisplayContext.FUEL_VALUES, connection.fuelValues()); + } + return builder.create(SlotDisplayContext.CONTEXT); + } + + private static EntryIngredient resolveSlotDisplay(SlotDisplay slot) { + try { + return ofItemStacks(slot.resolveForStacks(slotDisplayContext())); + } catch (Exception e) { + InternalLogger.getInstance().warn("Failed to resolve slot display: " + slot, e); + return EntryIngredient.empty(); + } + } public static List ofSlotDisplays(Iterable slots) { if (slots instanceof Collection collection && collection.isEmpty()) return Collections.emptyList(); diff --git a/architectury-19.0.9999.jar b/architectury-19.0.9999.jar new file mode 100644 index 000000000..732a53185 Binary files /dev/null and b/architectury-19.0.9999.jar differ diff --git a/build.gradle b/build.gradle index 38cf9e222..08a2d0742 100755 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,5 @@ plugins { - id("architectury-plugin") version("3.4-SNAPSHOT") - id("dev.architectury.loom") version("1.13-SNAPSHOT") apply false - id("org.cadixdev.licenser") version("0.6.1") - id("me.shedaniel.unified-publishing") version("0.1.+") + id("net.fabricmc.fabric-loom") version("${loom_version}") id("maven-publish") } @@ -10,165 +7,107 @@ import java.text.SimpleDateFormat def runNumber = System.getenv("GITHUB_RUN_NUMBER") ?: "9999" version = rootProject.base_version + "." + runNumber + (rootProject.unstable.toBoolean() ? "-alpha" : "") - group = "me.shedaniel" -subprojects { - apply plugin: "me.shedaniel.unified-publishing" - apply plugin: "java" - apply plugin: "dev.architectury.loom" +base { + archivesName = "${rootProject.name}-fabric" +} - sourceCompatibility = targetCompatibility = 1.8 +repositories { + maven { url "https://maven.shedaniel.me" } + maven { url "https://maven.architectury.dev" } + maven { url "https://maven.terraformersmc.com/releases" } +} - java { - withSourcesJar() +sourceSets { + main { + java.srcDirs = [ + "api/src/main/java", + "runtime/src/main/java", + "default-plugin/src/main/java", + "fabric/src/main/java" + ] + resources.srcDirs = [ + "runtime/src/main/resources", + "fabric/src/main/resources" + ] + } + test { + java.srcDirs = [ + "runtime/src/test/java" + ] } +} - loom { - silentMojangMappingsLicense() - } +loom { + noIntermediateMappings() + enableTransitiveAccessWideners = false + accessWidenerPath = file("fabric/src/main/resources/roughlyenoughitems.accessWidener") - repositories { - maven { url "https://maven.neoforged.net/releases/" } - if (rootProject.neoforge_pr != "") { - maven { - url "https://prmaven.neoforged.net/NeoForge/pr$rootProject.neoforge_pr" - content { - includeModule("net.neoforged", "neoforge") - } - } + mods { + roughlyenoughitems { + sourceSet sourceSets.main } } +} - dependencies { - minecraft("com.mojang:minecraft:${rootProject.minecraft_version}") - mappings(loom.layered { - officialMojangMappings() - parchment("org.parchmentmc.data:parchment-1.17.1:2021.10.10@zip") - crane("dev.architectury:crane:1.17+build.11") - }) - } - - jar { - from rootProject.file("LICENSE") - } +dependencies { + minecraft("com.mojang:minecraft:${project.minecraft_version}") - tasks.withType(JavaCompile) { - options.encoding = "UTF-8" - options.release = 21 + implementation("net.fabricmc:fabric-loader:${project.fabricloader_version}") + implementation("net.fabricmc.fabric-api:fabric-api:${project.fabric_api}") + implementation("me.shedaniel.cloth:cloth-config-fabric:${cloth_config_version}") { + exclude(module: "fabric-api") } -} + // Temporary migration bridge: + // the 26.1 branch still compiles against a locally remapped Architectury classes jar. + // The shim sources under fabric/src/main/java/dev/architectury/injectables/targets keep + // dev-time target resolution working until this can move back to a published dependency. + implementation(files("architectury-19.0.9999.jar")) -subprojects { - if (project.path == ':fabric' || project.path == ':forge' || project.path == ':neoforge') { - loom { - mods { - main { // to match the default mod generated for Forge - sourceSet project.sourceSets.main - def depProjects = [":api", ":runtime", ":default-plugin"] - depProjects.each { - sourceSet project(it).sourceSets.main - } - } - } - } - } + testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } -allprojects { - apply plugin: "maven-publish" - apply plugin: "architectury-plugin" - apply plugin: "org.cadixdev.licenser" - - architectury { - compileOnly() - } +processResources { + inputs.property "version", project.version - repositories { - maven { url "https://maven.shedaniel.me" } - maven { url "https://maven.parchmentmc.org" } - maven { url "https://maven.terraformersmc.com/releases" } + filesMatching("fabric.mod.json") { + expand "version": project.version } +} - license { - header rootProject.file("HEADER") - include "**/*.java" - - ext { - name = "shedaniel" - year = "2018, 2019, 2020, 2021, 2022, 2023" - } - } +tasks.withType(JavaCompile).configureEach { + options.encoding = "UTF-8" + options.release = 25 +} - tasks.withType(GenerateModuleMetadata) { - enabled = false - } +test { + useJUnitPlatform() } -["api", "default-plugin", "runtime"].forEach { - project(":fabric").evaluationDependsOn(":$it") +java { + withSourcesJar() + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 } -subprojects { - group = rootProject.group - version = rootProject.version - archivesBaseName = rootProject.name - - publishing { - repositories { - if (System.getenv("MAVEN_PASS") != null) { - maven { - url = "https://deploy.shedaniel.me/" - credentials { - username = "shedaniel" - password = System.getenv("MAVEN_PASS") - } - } - } - } +jar { + from("LICENSE") { + rename { "${it}_${project.base.archivesName.get()}" } } } -task licenseFormatAll -subprojects { licenseFormatAll.dependsOn("${path}:licenseFormat") } - -ext { - releaseChangelog = "No changelog" +tasks.named("validateAccessWidener").configure { + enabled = false } -/* Thank you modmenu & fablabs */ -task releaseOnCf { - def df = new SimpleDateFormat("yyyy-MM-dd HH:mm") - df.setTimeZone(TimeZone.getTimeZone("UTC")) - def branch - if (System.env.BRANCH_NAME) { - branch = System.env.BRANCH_NAME - branch = branch.substring(branch.lastIndexOf("/") + 1) - } else { - branch = "git rev-parse --abbrev-ref HEAD".execute().in.text.trim() - } - if (branch == "HEAD") { - branch = "git rev-parse --short HEAD".execute().in.text.trim() - } - def time = df.format(new Date()) - def changes = new StringBuilder() - changes << "## REI v$project.version for $project.supported_version\nUpdated at **$time**.\n[Click here for changelog](https://www.github.com/shedaniel/RoughlyEnoughItems/commits/$branch)" - def proc = "git log --max-count=200 --pretty=format:%s".execute() - proc.in.eachLine { line -> - def processedLine = line.toString() - if (!processedLine.contains("New translations") && !processedLine.contains("Merge") && !processedLine.contains("branch")) { - changes << "\n- ${processedLine.capitalize()}" +publishing { + publications { + create("mavenJava", MavenPublication) { + artifactId = project.base.archivesName.get() + from components.java } } - proc.waitFor() - releaseChangelog = changes.toString() - if (subprojects.any { it.name == "forge" }) { - dependsOn project("forge").tasks.getByName("publishUnified") - } - if (subprojects.any { it.name == "neoforge" }) { - dependsOn project("neoforge").tasks.getByName("publishUnified") - } - if (subprojects.any { it.name == "fabric" }) { - dependsOn project("fabric").tasks.getByName("publishUnified") - } } diff --git a/default-plugin/build.gradle b/default-plugin/build.gradle index f696ad920..a78574516 100644 --- a/default-plugin/build.gradle +++ b/default-plugin/build.gradle @@ -1,13 +1,15 @@ -archivesBaseName = rootProject.name + "-" + project.name +base { + archivesName = rootProject.name + "-" + project.name +} loom { accessWidenerPath = gradle.rootProject.project("fabric").file("src/main/resources/roughlyenoughitems.accessWidener") } dependencies { - modCompileOnly("net.fabricmc:fabric-loader:${project.fabricloader_version}") - modApi("me.shedaniel.cloth:cloth-config:${cloth_config_version}") - modApi("dev.architectury:architectury:${architectury_version}") + compileOnly("net.fabricmc:fabric-loader:${project.fabricloader_version}") + api("me.shedaniel.cloth:cloth-config:${cloth_config_version}") + api("dev.architectury:architectury:${architectury_version}") compileOnly(project(path: ":api", configuration: "namedElements")) } diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/DefaultClientPlugin.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/DefaultClientPlugin.java index dc6553079..6a30ea780 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/DefaultClientPlugin.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/DefaultClientPlugin.java @@ -47,6 +47,7 @@ import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; import me.shedaniel.rei.api.common.util.EntryIngredients; import me.shedaniel.rei.api.common.util.EntryStacks; +import me.shedaniel.rei.impl.common.InternalLogger; import me.shedaniel.rei.impl.ClientInternals; import me.shedaniel.rei.plugin.autocrafting.InventoryCraftingTransferHandler; import me.shedaniel.rei.plugin.autocrafting.recipebook.DefaultRecipeBookHandler; @@ -59,6 +60,8 @@ import me.shedaniel.rei.plugin.client.categories.tag.DefaultTagCategory; import me.shedaniel.rei.plugin.client.displays.ClientsidedCookingDisplay; import me.shedaniel.rei.plugin.client.displays.ClientsidedCraftingDisplay; +import me.shedaniel.rei.plugin.client.displays.ClientsidedSmithingDisplay; +import me.shedaniel.rei.plugin.client.displays.ClientsidedStoneCuttingDisplay; import me.shedaniel.rei.plugin.client.exclusionzones.DefaultPotionEffectExclusionZones; import me.shedaniel.rei.plugin.client.exclusionzones.DefaultRecipeBookExclusionZones; import me.shedaniel.rei.plugin.client.favorites.GameModeFavoriteEntry; @@ -97,8 +100,11 @@ import net.minecraft.world.item.alchemy.PotionContents; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.display.FurnaceRecipeDisplay; +import net.minecraft.world.item.crafting.display.RecipeDisplayId; import net.minecraft.world.item.crafting.display.ShapedCraftingRecipeDisplay; import net.minecraft.world.item.crafting.display.ShapelessCraftingRecipeDisplay; +import net.minecraft.world.item.crafting.display.SmithingRecipeDisplay; +import net.minecraft.world.item.crafting.display.StonecutterRecipeDisplay; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.EnchantmentInstance; @@ -123,6 +129,8 @@ @Environment(EnvType.CLIENT) @ApiStatus.Internal public class DefaultClientPlugin implements REIClientPlugin, BuiltinClientPlugin { + private static Boolean fluidEntriesAvailable; + public DefaultClientPlugin() { ClientInternals.attachInstance((Supplier) () -> this, "builtinClientPlugin"); } @@ -168,10 +176,12 @@ public void registerEntries(EntryRegistry registry) { } } - for (Fluid fluid : BuiltInRegistries.FLUID) { - FluidState state = fluid.defaultFluidState(); - if (!state.isEmpty() && state.isSource()) { - registry.addEntry(EntryStacks.of(fluid)); + if (canUseFluidEntries()) { + for (Fluid fluid : BuiltInRegistries.FLUID) { + FluidState state = fluid.defaultFluidState(); + if (!state.isEmpty() && state.isSource()) { + registry.addEntry(EntryStacks.of(fluid)); + } } } } @@ -288,29 +298,56 @@ public void registerCategories(CategoryRegistry registry) { private static EntryIngredient getTag(Identifier tagId) { return EntryIngredients.ofItemTag(TagKey.create(Registries.ITEM, tagId)); } + + private static boolean hasCraftingStation(FurnaceRecipeDisplay display, Item station) { + return EntryIngredients.testFuzzy(EntryIngredients.ofSlotDisplay(display.craftingStation()), EntryStacks.of(station)); + } + + private static ClientsidedCookingDisplay createCookingFallbackDisplay(FurnaceRecipeDisplay display, Optional id) { + if (hasCraftingStation(display, Items.SMOKER)) { + return new ClientsidedCookingDisplay.Smoking(display, id); + } + if (hasCraftingStation(display, Items.BLAST_FURNACE)) { + return new ClientsidedCookingDisplay.Blasting(display, id); + } + // Mojang can omit or flatten the workstation slot in recipe-book fallback displays on multiplayer. + // When that happens, keep the recipe visible by treating it as a normal furnace recipe. + return new ClientsidedCookingDisplay.Smelting(display, id); + } + + private static boolean canUseFluidEntries() { + if (fluidEntriesAvailable != null) { + return fluidEntriesAvailable; + } + + try { + EntryStacks.of(BuiltInRegistries.FLUID.getValue(Identifier.withDefaultNamespace("water"))); + fluidEntriesAvailable = true; + } catch (Throwable throwable) { + fluidEntriesAvailable = false; + InternalLogger.getInstance().warn("Disabling REI fluid entries because Architectury FluidStack failed to initialize.", throwable); + } + + return fluidEntriesAvailable; + } @Override public void registerDisplays(DisplayRegistry registry) { CategoryRegistry.getInstance().add(new DefaultInformationCategory(), new DefaultTagCategory()); registry.beginRecipeFiller(ShapedCraftingRecipeDisplay.class) - .filterType(ShapedCraftingRecipeDisplay.TYPE) .fill(ClientsidedCraftingDisplay.Shaped::new); registry.beginRecipeFiller(ShapelessCraftingRecipeDisplay.class) - .filterType(ShapelessCraftingRecipeDisplay.TYPE) .fill(ClientsidedCraftingDisplay.Shapeless::new); registry.beginRecipeFiller(FurnaceRecipeDisplay.class) .filterType(FurnaceRecipeDisplay.TYPE) - .filter((display, r) -> EntryIngredients.ofSlotDisplay(display.craftingStation()).contains(EntryStacks.of(Items.FURNACE))) - .fill(ClientsidedCookingDisplay.Smelting::new); - registry.beginRecipeFiller(FurnaceRecipeDisplay.class) - .filterType(FurnaceRecipeDisplay.TYPE) - .filter((display, r) -> EntryIngredients.ofSlotDisplay(display.craftingStation()).contains(EntryStacks.of(Items.SMOKER))) - .fill(ClientsidedCookingDisplay.Smoking::new); - registry.beginRecipeFiller(FurnaceRecipeDisplay.class) - .filterType(FurnaceRecipeDisplay.TYPE) - .filter((display, r) -> EntryIngredients.ofSlotDisplay(display.craftingStation()).contains(EntryStacks.of(Items.BLAST_FURNACE))) - .fill(ClientsidedCookingDisplay.Blasting::new); + .fill(DefaultClientPlugin::createCookingFallbackDisplay); + registry.beginRecipeFiller(StonecutterRecipeDisplay.class) + .filterType(StonecutterRecipeDisplay.TYPE) + .fill(ClientsidedStoneCuttingDisplay::new); + registry.beginRecipeFiller(SmithingRecipeDisplay.class) + .filterType(SmithingRecipeDisplay.TYPE) + .fill(ClientsidedSmithingDisplay::new); registry.beginFiller(AnvilRecipe.class) .fill(DefaultAnvilDisplay::new); registry.beginFiller(BrewingRecipe.class) @@ -322,6 +359,9 @@ public void registerDisplays(DisplayRegistry registry) { } else if (tagKey.isFor(Registries.BLOCK)) { return DefaultTagDisplay.ofItems(tagKey); } else if (tagKey.isFor(Registries.FLUID)) { + if (!canUseFluidEntries()) { + return null; + } return DefaultTagDisplay.ofFluids(tagKey); } diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/ArmorDyeRecipeFiller.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/ArmorDyeRecipeFiller.java deleted file mode 100644 index ceca15ac8..000000000 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/ArmorDyeRecipeFiller.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * This file is licensed under the MIT License, part of Roughly Enough Items. - * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package me.shedaniel.rei.plugin.client.categories.crafting.filler; - -import me.shedaniel.rei.api.common.display.Display; -import me.shedaniel.rei.api.common.entry.EntryIngredient; -import me.shedaniel.rei.api.common.entry.EntryStack; -import me.shedaniel.rei.api.common.util.EntryIngredients; -import me.shedaniel.rei.api.common.util.EntryStacks; -import me.shedaniel.rei.plugin.common.displays.crafting.DefaultCustomShapelessDisplay; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.tags.ItemTags; -import net.minecraft.world.item.DyeColor; -import net.minecraft.world.item.DyeItem; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.component.DyedItemColor; -import net.minecraft.world.item.crafting.ArmorDyeRecipe; -import net.minecraft.world.item.crafting.RecipeHolder; - -import java.util.*; - -public class ArmorDyeRecipeFiller implements CraftingRecipeFiller { - @Override - public Collection apply(RecipeHolder recipe) { - List displays = new ArrayList<>(); - List> toDye = BuiltInRegistries.ITEM.stream() - .filter(item -> item.builtInRegistryHolder().is(ItemTags.DYEABLE)) - .map(EntryStacks::of) - .>map(EntryStack::cast) - .toList(); - DyeColor[] colors = DyeColor.values(); - - for (EntryStack armor : toDye) { - ItemStack armorStack = armor.castValue(); - for (DyeColor color : colors) { - ItemStack output = armorStack.copy(); - DyeItem dyeItem = DyeItem.byColor(color); - output = DyedItemColor.applyDyes(output, List.of(dyeItem)); - displays.add(new DefaultCustomShapelessDisplay( - List.of(EntryIngredient.of(armor.copy()), - EntryIngredients.of(dyeItem)), - List.of(EntryIngredients.of(output)), - Optional.of(recipe.id().identifier()))); - } - - for (int i = 0; i < 9; i++) { - int dyes = new Random().nextInt(2) + 2; - List inputs = new ArrayList<>(); - List dyeItems = new ArrayList<>(); - inputs.add(EntryIngredient.of(armor.copy())); - for (int j = 0; j < dyes; j++) { - DyeColor color = colors[new Random().nextInt(colors.length)]; - DyeItem dyeItem = DyeItem.byColor(color); - dyeItems.add(dyeItem); - inputs.add(EntryIngredients.of(dyeItem)); - } - ItemStack output = armorStack.copy(); - output = DyedItemColor.applyDyes(output, dyeItems); - displays.add(new DefaultCustomShapelessDisplay( - inputs, List.of(EntryIngredients.of(output)), - Optional.of(recipe.id().identifier()))); - } - } - - return displays; - } - - @Override - public Class getRecipeClass() { - return ArmorDyeRecipe.class; - } -} diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/BookCloningRecipeFiller.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/BookCloningRecipeFiller.java index e0061ce82..01254f00c 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/BookCloningRecipeFiller.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/BookCloningRecipeFiller.java @@ -75,7 +75,7 @@ public Collection apply(RecipeHolder recipe) { inputs[k + 1].add(EntryStacks.of(bookAndQuill)); } ItemStack cloned = writtenBook.copy(); - cloned.update(DataComponents.WRITTEN_BOOK_CONTENT, WrittenBookContent.EMPTY, WrittenBookContent::tryCraftCopy); + cloned.update(DataComponents.WRITTEN_BOOK_CONTENT, WrittenBookContent.EMPTY, WrittenBookContent::craftCopy); cloned.setCount(i); output.add(EntryStacks.of(cloned)); } diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/TippedArrowRecipeFiller.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/TippedArrowRecipeFiller.java deleted file mode 100644 index f474bcc70..000000000 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/TippedArrowRecipeFiller.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * This file is licensed under the MIT License, part of Roughly Enough Items. - * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package me.shedaniel.rei.plugin.client.categories.crafting.filler; - -import me.shedaniel.rei.api.common.display.Display; -import me.shedaniel.rei.api.common.display.basic.BasicDisplay; -import me.shedaniel.rei.api.common.entry.EntryIngredient; -import me.shedaniel.rei.api.common.util.EntryIngredients; -import me.shedaniel.rei.api.common.util.EntryStacks; -import me.shedaniel.rei.plugin.common.displays.crafting.DefaultCustomDisplay; -import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; -import net.minecraft.core.component.DataComponents; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.Identifier; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import net.minecraft.world.item.alchemy.PotionContents; -import net.minecraft.world.item.crafting.RecipeHolder; -import net.minecraft.world.item.crafting.TippedArrowRecipe; - -import java.util.*; - -public class TippedArrowRecipeFiller implements CraftingRecipeFiller { - @Override - public Collection apply(RecipeHolder recipe) { - EntryIngredient arrowStack = EntryIngredient.of(EntryStacks.of(Items.ARROW)); - Set registeredPotions = new HashSet<>(); - List displays = new ArrayList<>(); - - RegistryAccess registryAccess = BasicDisplay.registryAccess(); - BasicDisplay.registryAccess().lookup(Registries.POTION).stream() - .flatMap(Registry::listElements) - .map(reference -> PotionContents.createItemStack(Items.LINGERING_POTION, reference)) - .forEach(itemStack -> { - PotionContents potion = itemStack.get(DataComponents.POTION_CONTENTS); - if (potion.potion().isPresent() && potion.potion().get().unwrapKey().isPresent() && registeredPotions.add(potion.potion().get().unwrapKey().get().identifier())) { - List input = new ArrayList<>(); - for (int i = 0; i < 4; i++) - input.add(arrowStack); - input.add(EntryIngredients.of(itemStack)); - for (int i = 0; i < 4; i++) - input.add(arrowStack); - ItemStack outputStack = new ItemStack(Items.TIPPED_ARROW, 8); - outputStack.set(DataComponents.POTION_CONTENTS, potion); - displays.add(new DefaultCustomDisplay(input, List.of(EntryIngredients.of(outputStack)), Optional.of(recipe.id().identifier()))); - } - }); - - return displays; - } - - @Override - public Class getRecipeClass() { - return TippedArrowRecipe.class; - } -} diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/displays/ClientsidedSmithingDisplay.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/displays/ClientsidedSmithingDisplay.java new file mode 100644 index 000000000..ce75e2b9b --- /dev/null +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/displays/ClientsidedSmithingDisplay.java @@ -0,0 +1,82 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.plugin.client.displays; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.display.DisplaySerializer; +import me.shedaniel.rei.api.common.entry.EntryIngredient; +import me.shedaniel.rei.api.common.util.EntryIngredients; +import me.shedaniel.rei.plugin.common.displays.DefaultSmithingDisplay; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.crafting.display.RecipeDisplayId; +import net.minecraft.world.item.crafting.display.SmithingRecipeDisplay; + +import java.util.List; +import java.util.Optional; + +public class ClientsidedSmithingDisplay extends DefaultSmithingDisplay implements ClientsidedRecipeBookDisplay { + public static final DisplaySerializer SERIALIZER = DisplaySerializer.of( + RecordCodecBuilder.mapCodec(instance -> instance.group( + EntryIngredient.codec().listOf().fieldOf("inputs").forGetter(ClientsidedSmithingDisplay::getInputEntries), + EntryIngredient.codec().listOf().fieldOf("outputs").forGetter(ClientsidedSmithingDisplay::getOutputEntries), + Codec.INT.xmap(RecipeDisplayId::new, RecipeDisplayId::index).optionalFieldOf("id").forGetter(ClientsidedSmithingDisplay::recipeDisplayId) + ).apply(instance, ClientsidedSmithingDisplay::new)), + StreamCodec.composite( + EntryIngredient.streamCodec().apply(ByteBufCodecs.list()), + ClientsidedSmithingDisplay::getInputEntries, + EntryIngredient.streamCodec().apply(ByteBufCodecs.list()), + ClientsidedSmithingDisplay::getOutputEntries, + ByteBufCodecs.optional(ByteBufCodecs.INT.map(RecipeDisplayId::new, RecipeDisplayId::index)), + ClientsidedSmithingDisplay::recipeDisplayId, + ClientsidedSmithingDisplay::new + ), false); + + private final Optional id; + + public ClientsidedSmithingDisplay(SmithingRecipeDisplay recipe, Optional id) { + this(List.of( + EntryIngredients.ofSlotDisplay(recipe.template()), + EntryIngredients.ofSlotDisplay(recipe.base()), + EntryIngredients.ofSlotDisplay(recipe.addition()) + ), List.of(EntryIngredients.ofSlotDisplay(recipe.result())), id); + } + + public ClientsidedSmithingDisplay(List inputs, List outputs, Optional id) { + super(inputs, outputs, Optional.empty(), Optional.empty()); + this.id = id; + } + + @Override + public Optional recipeDisplayId() { + return id; + } + + @Override + public DisplaySerializer getSerializer() { + return SERIALIZER; + } +} diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/displays/ClientsidedStoneCuttingDisplay.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/displays/ClientsidedStoneCuttingDisplay.java new file mode 100644 index 000000000..02196fb89 --- /dev/null +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/displays/ClientsidedStoneCuttingDisplay.java @@ -0,0 +1,78 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.plugin.client.displays; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.display.DisplaySerializer; +import me.shedaniel.rei.api.common.entry.EntryIngredient; +import me.shedaniel.rei.api.common.util.EntryIngredients; +import me.shedaniel.rei.plugin.common.displays.DefaultStoneCuttingDisplay; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.crafting.display.RecipeDisplayId; +import net.minecraft.world.item.crafting.display.StonecutterRecipeDisplay; + +import java.util.List; +import java.util.Optional; + +public class ClientsidedStoneCuttingDisplay extends DefaultStoneCuttingDisplay implements ClientsidedRecipeBookDisplay { + public static final DisplaySerializer SERIALIZER = DisplaySerializer.of( + RecordCodecBuilder.mapCodec(instance -> instance.group( + EntryIngredient.codec().listOf().fieldOf("inputs").forGetter(ClientsidedStoneCuttingDisplay::getInputEntries), + EntryIngredient.codec().listOf().fieldOf("outputs").forGetter(ClientsidedStoneCuttingDisplay::getOutputEntries), + Codec.INT.xmap(RecipeDisplayId::new, RecipeDisplayId::index).optionalFieldOf("id").forGetter(ClientsidedStoneCuttingDisplay::recipeDisplayId) + ).apply(instance, ClientsidedStoneCuttingDisplay::new)), + StreamCodec.composite( + EntryIngredient.streamCodec().apply(ByteBufCodecs.list()), + ClientsidedStoneCuttingDisplay::getInputEntries, + EntryIngredient.streamCodec().apply(ByteBufCodecs.list()), + ClientsidedStoneCuttingDisplay::getOutputEntries, + ByteBufCodecs.optional(ByteBufCodecs.INT.map(RecipeDisplayId::new, RecipeDisplayId::index)), + ClientsidedStoneCuttingDisplay::recipeDisplayId, + ClientsidedStoneCuttingDisplay::new + ), false); + + private final Optional id; + + public ClientsidedStoneCuttingDisplay(StonecutterRecipeDisplay recipe, Optional id) { + this(List.of(EntryIngredients.ofSlotDisplay(recipe.input())), List.of(EntryIngredients.ofSlotDisplay(recipe.result())), id); + } + + public ClientsidedStoneCuttingDisplay(List inputs, List outputs, Optional id) { + super(inputs, outputs, Optional.empty()); + this.id = id; + } + + @Override + public Optional recipeDisplayId() { + return id; + } + + @Override + public DisplaySerializer getSerializer() { + return SERIALIZER; + } +} diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/favorites/TimeFavoriteEntry.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/favorites/TimeFavoriteEntry.java index 79a52d051..d2b09e02c 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/favorites/TimeFavoriteEntry.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/favorites/TimeFavoriteEntry.java @@ -120,7 +120,7 @@ public int hashCode() { private Time nextTime() { ClientLevel level = Minecraft.getInstance().level; - long dayTime = level.getDayTime(); + long dayTime = level.getGameTime() % 24000L; if (dayTime <= 1000) { return Time.MORN; } else if (dayTime <= 6000) { diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/favorites/WeatherFavoriteEntry.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/favorites/WeatherFavoriteEntry.java index e53433726..6286f33c0 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/favorites/WeatherFavoriteEntry.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/favorites/WeatherFavoriteEntry.java @@ -105,7 +105,7 @@ private static Weather getCurrentWeather() { ClientLevel world = Minecraft.getInstance().level; if (world.isThundering()) return Weather.THUNDER; - if (world.getLevelData().isRaining()) + if (world.isRaining()) return Weather.RAIN; return Weather.CLEAR; } diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/DefaultPlugin.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/DefaultPlugin.java index 5b6da83b9..f75c2fca2 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/DefaultPlugin.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/DefaultPlugin.java @@ -35,6 +35,8 @@ import me.shedaniel.rei.plugin.client.categories.crafting.filler.*; import me.shedaniel.rei.plugin.client.displays.ClientsidedCookingDisplay; import me.shedaniel.rei.plugin.client.displays.ClientsidedCraftingDisplay; +import me.shedaniel.rei.plugin.client.displays.ClientsidedSmithingDisplay; +import me.shedaniel.rei.plugin.client.displays.ClientsidedStoneCuttingDisplay; import me.shedaniel.rei.plugin.common.displays.*; import me.shedaniel.rei.plugin.common.displays.anvil.DefaultAnvilDisplay; import me.shedaniel.rei.plugin.common.displays.beacon.DefaultBeaconBaseDisplay; @@ -63,13 +65,10 @@ @ApiStatus.Internal public class DefaultPlugin implements BuiltinPlugin, REICommonPlugin { private static final CraftingRecipeFiller[] CRAFTING_RECIPE_FILLERS = new CraftingRecipeFiller[]{ - new TippedArrowRecipeFiller(), new BannerDuplicateRecipeFiller(), new ShieldDecorationRecipeFiller(), new BookCloningRecipeFiller(), new FireworkRocketRecipeFiller(), - new ArmorDyeRecipeFiller(), - new MapCloningRecipeFiller(), new MapExtendingRecipeFiller() }; @@ -147,6 +146,8 @@ public void registerDisplaySerializer(DisplaySerializerRegistry registry) { registry.register(id("client/smelting"), ClientsidedCookingDisplay.Smelting.SERIALIZER); registry.register(id("client/smoking"), ClientsidedCookingDisplay.Smoking.SERIALIZER); registry.register(id("client/blasting"), ClientsidedCookingDisplay.Blasting.SERIALIZER); + registry.register(id("client/stone_cutting"), ClientsidedStoneCuttingDisplay.SERIALIZER); + registry.register(id("client/smithing"), ClientsidedSmithingDisplay.SERIALIZER); registry.register(id("default/crafting/shaped"), DefaultShapedDisplay.SERIALIZER); registry.register(id("default/crafting/shapeless"), DefaultShapelessDisplay.SERIALIZER); registry.register(id("default/crafting/custom"), DefaultCustomDisplay.SERIALIZER); diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultCampfireDisplay.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultCampfireDisplay.java index 2feaa7e04..ab458a4ed 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultCampfireDisplay.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultCampfireDisplay.java @@ -67,7 +67,7 @@ public class DefaultCampfireDisplay extends BasicDisplay implements CampfireDisp public DefaultCampfireDisplay(RecipeHolder recipe) { this(List.of(EntryIngredients.ofIngredient(recipe.value().input())), - List.of(EntryIngredients.of(recipe.value().result())), + List.of(EntryIngredients.ofSlotDisplay(recipe.value().display().getFirst().result())), Optional.of(recipe.id().identifier()), recipe.value().cookingTime()); } diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultSmithingDisplay.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultSmithingDisplay.java index d6455f303..c4fcfc81c 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultSmithingDisplay.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultSmithingDisplay.java @@ -91,7 +91,7 @@ public static DefaultSmithingDisplay ofTransforming(RecipeHolder trimMaterial = TrimMaterials.getFromIngredient(registryAccess, additionItem) + Holder trimMaterial = getMaterialFromIngredient(registryAccess, additionItem.typeHolder()) .orElse(null); if (trimMaterial == null) return EntryIngredient.empty(); ArmorTrim armorTrim = new ArmorTrim(trimMaterial, trimPattern); @@ -162,8 +162,8 @@ public static EntryIngredient getTrimmingOutput(RegistryAccess registryAccess, H } private static Optional> getMaterialFromIngredient(HolderLookup.Provider provider, Holder item) { - ProvidesTrimMaterial providesTrimMaterial = new ItemStack(item).get(DataComponents.PROVIDES_TRIM_MATERIAL); - return providesTrimMaterial != null ? providesTrimMaterial.unwrap(provider) : Optional.empty(); + Holder material = new ItemStack(item).get(DataComponents.PROVIDES_TRIM_MATERIAL); + return Optional.ofNullable(material); } public static class Trimming extends DefaultSmithingDisplay implements SmithingDisplay.Trimming { diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultStoneCuttingDisplay.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultStoneCuttingDisplay.java index 0b76eed7f..9243eae1f 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultStoneCuttingDisplay.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultStoneCuttingDisplay.java @@ -59,7 +59,7 @@ public class DefaultStoneCuttingDisplay extends BasicDisplay { public DefaultStoneCuttingDisplay(RecipeHolder recipe) { this(List.of(EntryIngredients.ofIngredient(recipe.value().input())), - List.of(EntryIngredients.of(recipe.value().result())), + List.of(EntryIngredients.ofSlotDisplay(recipe.value().resultDisplay())), Optional.of(recipe.id().identifier())); } diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/cooking/DefaultCookingDisplay.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/cooking/DefaultCookingDisplay.java index e58a4ef09..3e51d30bd 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/cooking/DefaultCookingDisplay.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/cooking/DefaultCookingDisplay.java @@ -46,7 +46,7 @@ public abstract class DefaultCookingDisplay extends BasicDisplay implements Cook public DefaultCookingDisplay(RecipeHolder recipe) { this(List.of(EntryIngredients.ofIngredient(recipe.value().input())), - List.of(EntryIngredients.of(recipe.value().result())), + List.of(EntryIngredients.ofSlotDisplay(recipe.value().display().getFirst().result())), Optional.of(recipe.id().identifier()), recipe.value().experience(), recipe.value().cookingTime()); } diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/crafting/DefaultShapedDisplay.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/crafting/DefaultShapedDisplay.java index b265e26f4..93fe3daa1 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/crafting/DefaultShapedDisplay.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/crafting/DefaultShapedDisplay.java @@ -68,7 +68,7 @@ public class DefaultShapedDisplay extends DefaultCraftingDisplay { public DefaultShapedDisplay(RecipeHolder recipe) { super( CollectionUtils.map(recipe.value().getIngredients(), opt -> opt.map(EntryIngredients::ofIngredient).orElse(EntryIngredient.empty())), - List.of(EntryIngredients.of(recipe.value().result)), + List.of(EntryIngredients.ofSlotDisplay(recipe.value().display().getFirst().result())), Optional.of(recipe.id().identifier()) ); this.width = recipe.value().getWidth(); diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/crafting/DefaultShapelessDisplay.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/crafting/DefaultShapelessDisplay.java index b4092ce4c..aa771b5bf 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/crafting/DefaultShapelessDisplay.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/crafting/DefaultShapelessDisplay.java @@ -58,7 +58,7 @@ public class DefaultShapelessDisplay extends DefaultCraftingDisplay { public DefaultShapelessDisplay(RecipeHolder recipe) { super( CollectionUtils.map(recipe.value().placementInfo().ingredients(), EntryIngredients::ofIngredient), - List.of(EntryIngredients.of(recipe.value().result)), + List.of(EntryIngredients.ofSlotDisplay(recipe.value().display().getFirst().result())), Optional.of(recipe.id().identifier()) ); } diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNodes.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNodes.java index da26c1bc5..e5a90024b 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNodes.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNodes.java @@ -215,7 +215,7 @@ public static void create(TagKey tagKey, Consumer>> private static Optional>> resolveTag(TagKey tagKey, Registry registry, Map tagDataMap) { TagData tagData = tagDataMap.get(tagKey.location()); - if (tagData == null) return Optional.empty(); + if (tagData == null) return resolveTagFromRegistry(tagKey, registry); TagNode self = TagNode.ofReference(tagKey); List> holders = new ArrayList<>(); @@ -242,4 +242,13 @@ private static Optional>> resolveTag(TagKey tagKey, } return Optional.of(DataResult.success(self)); } + + private static Optional>> resolveTagFromRegistry(TagKey tagKey, Registry registry) { + Optional> holders = registry.get(tagKey); + if (holders.isEmpty()) return Optional.empty(); + + TagNode self = TagNode.ofReference(tagKey); + self.addValuesChild(holders.get()); + return Optional.of(DataResult.success(self)); + } } diff --git a/fabric/build.gradle b/fabric/build.gradle index 89a33a01b..a69b75782 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -28,15 +28,13 @@ loom { def depProjects = [":api", ":runtime", ":default-plugin"] dependencies { - modApi("net.fabricmc:fabric-loader:${project.fabricloader_version}") - modApi("net.fabricmc.fabric-api:fabric-api:${project.fabric_api}") - modApi("me.shedaniel.cloth:cloth-config-fabric:${cloth_config_version}") { + implementation("net.fabricmc:fabric-loader:${project.fabricloader_version}") + implementation("net.fabricmc.fabric-api:fabric-api:${project.fabric_api}") + implementation("me.shedaniel.cloth:cloth-config-fabric:${cloth_config_version}") { exclude(module: "fabric-api") } //modRuntime("com.terraformersmc:modmenu:${modmenu_version}") { transitive false } - modApi("dev.architectury:architectury-fabric:${architectury_version}") - modApi("me.shedaniel:error-notifier-fabric:1.0.11") - include("me.shedaniel:error-notifier-fabric:1.0.11") + implementation("dev.architectury:architectury-fabric:${architectury_version}") depProjects.forEach { common(project(path: it, configuration: "namedElements")) { transitive false } @@ -121,7 +119,7 @@ unifiedPublishing { project { displayName = "[Fabric $rootProject.supported_version] v$project.version" releaseType = rootProject.unstable == "false" ? "release" : "alpha" - gameVersions = ["1.21.11"] + gameVersions = ["26.1"] gameLoaders = ["fabric"] changelog = rootProject.releaseChangelog @@ -146,7 +144,7 @@ unifiedPublishing { curseforge { token = project.hasProperty("danielshe_curse_api_key") ? project.property("danielshe_curse_api_key") : System.getenv("danielshe_curse_api_key") id = "310111" - gameVersions.addAll "Java 17" + gameVersions.addAll "Java 25" } } diff --git a/fabric/src/main/java/dev/architectury/injectables/targets/ArchitecturyTarget.java b/fabric/src/main/java/dev/architectury/injectables/targets/ArchitecturyTarget.java new file mode 100644 index 000000000..30b6f2d97 --- /dev/null +++ b/fabric/src/main/java/dev/architectury/injectables/targets/ArchitecturyTarget.java @@ -0,0 +1,15 @@ +package dev.architectury.injectables.targets; + +/** + * REI now consumes the published Fabric Architectury artifact directly. + * Keep exposing the Fabric target explicitly in dev until Architectury's + * normal target transform is restored for this migration branch. + */ +public final class ArchitecturyTarget { + private ArchitecturyTarget() { + } + + public static String getCurrentTarget() { + return "fabric"; + } +} diff --git a/fabric/src/main/java/me/shedaniel/rei/fabric/PlatformAdapterImpl.java b/fabric/src/main/java/me/shedaniel/rei/fabric/PlatformAdapterImpl.java index 7a5ceef59..46a84c427 100644 --- a/fabric/src/main/java/me/shedaniel/rei/fabric/PlatformAdapterImpl.java +++ b/fabric/src/main/java/me/shedaniel/rei/fabric/PlatformAdapterImpl.java @@ -34,17 +34,6 @@ public class PlatformAdapterImpl implements PlatformAdapter { @Override public EntryIngredient fromIngredient(Ingredient ingredient) { if (ingredient.isEmpty()) return EntryIngredient.empty(); - if (ingredient.getCustomIngredient() != null) { - EntryIngredient.Builder result = EntryIngredient.builder(); - ingredient.items().forEach(item -> { - EntryStack stack = EntryStacks.ofItemHolder(item); - if (!stack.isEmpty()) { - result.add(stack); - } - }); - return result.build(); - } else { - return EntryIngredients.ofItemsHolderSet(ingredient.values); - } + return EntryIngredients.ofSlotDisplay(ingredient.display()); } } diff --git a/fabric/src/main/java/me/shedaniel/rei/impl/client/fabric/CreativeModeTabCollectorImpl.java b/fabric/src/main/java/me/shedaniel/rei/impl/client/fabric/CreativeModeTabCollectorImpl.java index 660ebefaa..facc732c3 100644 --- a/fabric/src/main/java/me/shedaniel/rei/impl/client/fabric/CreativeModeTabCollectorImpl.java +++ b/fabric/src/main/java/me/shedaniel/rei/impl/client/fabric/CreativeModeTabCollectorImpl.java @@ -25,8 +25,8 @@ import me.shedaniel.rei.api.common.display.basic.BasicDisplay; import me.shedaniel.rei.impl.common.InternalLogger; -import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroupEntries; -import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; +import net.fabricmc.fabric.api.creativetab.v1.CreativeModeTabEvents; +import net.fabricmc.fabric.api.creativetab.v1.FabricCreativeModeTabOutput; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceKey; import net.minecraft.world.flag.FeatureFlagSet; @@ -65,9 +65,9 @@ public static Map> collectTabs() { private static Collection postFabricEvents(CreativeModeTab tab, CreativeModeTab.ItemDisplayParameters parameters, ResourceKey resourceKey, Collection tabContents) { try { // Sorry! - FabricItemGroupEntries entries = new FabricItemGroupEntries(parameters, new LinkedList<>(tabContents), new LinkedList<>()); - ItemGroupEvents.modifyEntriesEvent(resourceKey).invoker().modifyEntries(entries); - ItemGroupEvents.MODIFY_ENTRIES_ALL.invoker().modifyEntries(tab, entries); + FabricCreativeModeTabOutput entries = new FabricCreativeModeTabOutput(parameters, new LinkedList<>(tabContents), new LinkedList<>()); + CreativeModeTabEvents.modifyOutputEvent(resourceKey).invoker().modifyOutput(entries); + CreativeModeTabEvents.MODIFY_OUTPUT_ALL.invoker().modifyOutput(tab, entries); return entries.getDisplayStacks(); } catch (Throwable throwable) { InternalLogger.getInstance().error("Failed to collect fabric's creative tab: " + tab, throwable); diff --git a/fabric/src/main/java/me/shedaniel/rei/impl/client/gui/fabric/ScreenOverlayImplFabric.java b/fabric/src/main/java/me/shedaniel/rei/impl/client/gui/fabric/ScreenOverlayImplFabric.java index 7bd7351b3..4b42ee401 100644 --- a/fabric/src/main/java/me/shedaniel/rei/impl/client/gui/fabric/ScreenOverlayImplFabric.java +++ b/fabric/src/main/java/me/shedaniel/rei/impl/client/gui/fabric/ScreenOverlayImplFabric.java @@ -76,7 +76,7 @@ public static void renderTooltipInner(GuiGraphics graphics, List rei$getKnownEntries(); +} diff --git a/fabric/src/main/java/me/shedaniel/rei/mixin/fabric/MixinEffectsInInventory.java b/fabric/src/main/java/me/shedaniel/rei/mixin/fabric/MixinEffectsInInventory.java index 5a92d9339..f0f67957a 100644 --- a/fabric/src/main/java/me/shedaniel/rei/mixin/fabric/MixinEffectsInInventory.java +++ b/fabric/src/main/java/me/shedaniel/rei/mixin/fabric/MixinEffectsInInventory.java @@ -23,47 +23,9 @@ package me.shedaniel.rei.mixin.fabric; -import me.shedaniel.rei.api.client.config.ConfigObject; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.EffectsInInventory; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.*; @Mixin(EffectsInInventory.class) public abstract class MixinEffectsInInventory { - @Shadow - @Final - private AbstractContainerScreen screen; - - @Unique - private boolean leftSideEffects() { - return ConfigObject.getInstance().isLeftSideMobEffects(); - } - - @ModifyVariable( - method = "renderEffects", - at = @At("HEAD"), - argsOnly = true, - ordinal = 4 // 'k' is the 5th parameter (0-based: i=0, j=1, k=2, l=3, m=4?) adjust if needed - ) - private int modifyKParam(int k) { - if (!leftSideEffects()) return k; - return this.screen.leftPos >= 120 ? this.screen.leftPos - 120 - 4 : this.screen.leftPos - 32 - 4; - } - - @ModifyArg( - method = "renderEffects", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/client/gui/screens/inventory/EffectsInInventory;renderBackground(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/Component;IIZI)I" - ), - index = 6 - ) - private boolean modifyRenderBackgroundBl(boolean bl) { - if (!leftSideEffects()) return bl; - return this.screen.leftPos >= 120; - } } diff --git a/fabric/src/main/java/net/minecraft/client/gui/GuiGraphics.java b/fabric/src/main/java/net/minecraft/client/gui/GuiGraphics.java new file mode 100644 index 000000000..28da9c0a1 --- /dev/null +++ b/fabric/src/main/java/net/minecraft/client/gui/GuiGraphics.java @@ -0,0 +1,178 @@ +package net.minecraft.client.gui; + +import net.minecraft.client.Minecraft; +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.textures.GpuSampler; +import com.mojang.blaze3d.textures.GpuTextureView; +import net.minecraft.client.renderer.state.gui.GuiRenderState; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.world.item.ItemStack; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Compatibility shim for code that still targets the pre-26.1 GuiGraphics type. + */ +public class GuiGraphics extends GuiGraphicsExtractor { + private static final Field MINECRAFT_FIELD = findField("minecraft"); + private static final Field GUI_RENDER_STATE_FIELD = findField("guiRenderState"); + private static final Field MOUSE_X_FIELD = findField("mouseX"); + private static final Field MOUSE_Y_FIELD = findField("mouseY"); + private static final Field SCISSOR_STACK_FIELD = findField("scissorStack"); + private static final Method INNER_TEXTURED_BLIT_METHOD = findMethod("innerBlit", + RenderPipeline.class, GpuTextureView.class, GpuSampler.class, + int.class, int.class, int.class, int.class, + float.class, float.class, float.class, float.class, int.class); + + public GuiGraphics(Minecraft minecraft, GuiRenderState renderState, int guiWidth, int guiHeight) { + super(minecraft, renderState, guiWidth, guiHeight); + } + + public GuiGraphics(GuiGraphicsExtractor extractor) { + this(readField(MINECRAFT_FIELD, extractor), readField(GUI_RENDER_STATE_FIELD, extractor), + readIntField(MOUSE_X_FIELD, extractor), readIntField(MOUSE_Y_FIELD, extractor)); + } + + public void drawString(Font font, FormattedCharSequence text, int x, int y, int color) { + text(font, text, x, y, color); + } + + public void drawString(Font font, FormattedCharSequence text, int x, int y, int color, boolean shadow) { + text(font, text, x, y, color, shadow); + } + + public void drawString(Font font, Component text, int x, int y, int color) { + text(font, text, x, y, color); + } + + public void drawString(Font font, Component text, int x, int y, int color, boolean shadow) { + text(font, text, x, y, color, shadow); + } + + public void drawString(Font font, String text, int x, int y, int color) { + text(font, text, x, y, color); + } + + public void drawString(Font font, String text, int x, int y, int color, boolean shadow) { + text(font, text, x, y, color, shadow); + } + + public void drawCenteredString(Font font, String text, int x, int y, int color) { + centeredText(font, text, x, y, color); + } + + public void drawCenteredString(Font font, Component text, int x, int y, int color) { + centeredText(font, text, x, y, color); + } + + public void drawWordWrap(Font font, Component text, int x, int y, int width, int color) { + textWithWordWrap(font, text, x, y, width, color); + } + + public void renderDeferredElements() { + extractDeferredElements(readIntField(MOUSE_X_FIELD, this), readIntField(MOUSE_Y_FIELD, this), 0.0F); + } + + public void renderOutline(int x, int y, int width, int height, int color) { + outline(x, y, width, height, color); + } + + public void hLine(int minX, int maxX, int y, int color) { + horizontalLine(minX, maxX, y, color); + } + + public void vLine(int x, int minY, int maxY, int color) { + verticalLine(x, minY, maxY, color); + } + + public void renderItem(ItemStack stack, int x, int y) { + item(stack, x, y); + } + + public void renderItem(ItemStack stack, int x, int y, int seed) { + item(stack, x, y, seed); + } + + public void renderItemDecorations(Font font, ItemStack stack, int x, int y) { + itemDecorations(font, stack, x, y); + } + + public void renderItemDecorations(Font font, ItemStack stack, int x, int y, String text) { + itemDecorations(font, stack, x, y, text); + } + + public void innerBlit(RenderPipeline pipeline, Identifier location, int xStart, int xEnd, int yStart, int yEnd, float u0, float u1, float v0, float v1, int color) { + Minecraft minecraft = readField(MINECRAFT_FIELD, this); + AbstractTexture texture = minecraft.getTextureManager().getTexture(location); + invoke(INNER_TEXTURED_BLIT_METHOD, this, pipeline, texture.getTextureView(), texture.getSampler(), + xStart, yStart, xEnd, yEnd, u0, u1, v0, v1, color); + } + + public void withFreshScissorStack(Runnable runnable) { + Object previous = readField(SCISSOR_STACK_FIELD, this); + try { + Constructor constructor = SCISSOR_STACK_FIELD.getType().getDeclaredConstructor(); + constructor.setAccessible(true); + SCISSOR_STACK_FIELD.set(this, constructor.newInstance()); + runnable.run(); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to swap GuiGraphicsExtractor scissor stack", e); + } finally { + try { + SCISSOR_STACK_FIELD.set(this, previous); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to restore GuiGraphicsExtractor scissor stack", e); + } + } + } + + private static Field findField(String name) { + try { + Field field = GuiGraphicsExtractor.class.getDeclaredField(name); + field.setAccessible(true); + return field; + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to resolve GuiGraphicsExtractor field: " + name, e); + } + } + + private static Method findMethod(String name, Class... parameterTypes) { + try { + Method method = GuiGraphicsExtractor.class.getDeclaredMethod(name, parameterTypes); + method.setAccessible(true); + return method; + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to resolve GuiGraphicsExtractor method: " + name, e); + } + } + + @SuppressWarnings("unchecked") + private static T readField(Field field, Object instance) { + try { + return (T) field.get(instance); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to read GuiGraphicsExtractor field: " + field.getName(), e); + } + } + + private static int readIntField(Field field, Object instance) { + try { + return field.getInt(instance); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to read GuiGraphicsExtractor field: " + field.getName(), e); + } + } + + private static void invoke(Method method, Object instance, Object... args) { + try { + method.invoke(instance, args); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to invoke GuiGraphicsExtractor method: " + method.getName(), e); + } + } +} diff --git a/fabric/src/main/resources/error_notifier.json b/fabric/src/main/resources/error_notifier.json index d44c1f187..71b199629 100644 --- a/fabric/src/main/resources/error_notifier.json +++ b/fabric/src/main/resources/error_notifier.json @@ -3,7 +3,7 @@ "checks": [ { "type": "depends", - "modId": "fabric", + "modId": "fabric-api", "modName": "Fabric API", "versions": "*", "url": "https://www.curseforge.com/minecraft/mc-mods/fabric-api/" @@ -12,14 +12,14 @@ "type": "depends", "modId": "architectury", "modName": "Architectury API", - "versions": ">=18.0.0 <20.0.0", + "versions": ">=19.0.1 <20.0.0", "url": "https://www.curseforge.com/minecraft/mc-mods/architectury-api/" }, { "type": "depends", "modId": "cloth-config2", "modName": "Cloth Config", - "versions": ">=20.0.0 <22.0.0", + "versions": ">=26.1.154 <27.0.0", "url": "https://www.curseforge.com/minecraft/mc-mods/cloth-config/" } ] diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index df868f9d8..d806303a2 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -14,6 +14,7 @@ }, "license": "MIT", "icon": "icon.png", + "environment": "*", "entrypoints": { "main": [ "me.shedaniel.rei.impl.init.RoughlyEnoughItemsInitializer::onInitialize" @@ -36,9 +37,6 @@ "mixins": [ "rei.mixins.json" ], - "breaks": { - "cloth-config2": "<6.2-" - }, "custom": { "rei:translators": { "English": [ @@ -119,7 +117,10 @@ } }, "depends": { - "minecraft": "~1.21-", - "architectury": ">=15.0.2" + "fabricloader": ">=0.18.5", + "minecraft": "~26.1", + "java": ">=25", + "fabric-api": "*", + "architectury": ">=19.0.1" } } diff --git a/fabric/src/main/resources/rei.mixins.json b/fabric/src/main/resources/rei.mixins.json index 85be50e95..a332d76e4 100644 --- a/fabric/src/main/resources/rei.mixins.json +++ b/fabric/src/main/resources/rei.mixins.json @@ -2,9 +2,10 @@ "required": true, "package": "me.shedaniel.rei.mixin.fabric", "plugin": "me.shedaniel.rei.mixin.fabric.REIMixinPlugin", - "compatibilityLevel": "JAVA_8", + "compatibilityLevel": "JAVA_25", "client": [ "MixinClientPacketListener", + "MixinClientRecipeBook", "MixinEffectsInInventory", "MixinInputConstants", "MixinInputConstantsKey", diff --git a/fabric/src/main/resources/roughlyenoughitems.accessWidener b/fabric/src/main/resources/roughlyenoughitems.accessWidener index f6122e6a4..25075f52a 100644 --- a/fabric/src/main/resources/roughlyenoughitems.accessWidener +++ b/fabric/src/main/resources/roughlyenoughitems.accessWidener @@ -1,4 +1,4 @@ -accessWidener v1 named +accessWidener v1 official accessible class net/minecraft/world/item/alchemy/PotionBrewing$Mix accessible field net/minecraft/client/gui/components/ImageButton sprites Lnet/minecraft/client/gui/components/WidgetSprites; accessible field net/minecraft/client/gui/screens/inventory/AbstractContainerScreen hoveredSlot Lnet/minecraft/world/inventory/Slot; @@ -45,4 +45,4 @@ accessible class net/minecraft/client/gui/GuiGraphics$ScissorStack accessible field net/minecraft/client/gui/GuiGraphics$ScissorStack stack Ljava/util/Deque; accessible method net/minecraft/client/gui/GuiGraphics$ScissorStack ()V accessible method net/minecraft/client/gui/screens/Screen defaultHandleGameClickEvent (Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/client/Minecraft;Lnet/minecraft/client/gui/screens/Screen;)V -accessible class net/minecraft/client/StringSplitter$WidthLimitedCharSink \ No newline at end of file +accessible class net/minecraft/client/StringSplitter$WidthLimitedCharSink diff --git a/gradle.properties b/gradle.properties index 427dc1ad3..e67d6b8bb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,16 +1,17 @@ org.gradle.jvmargs=-Xmx6G -base_version=21.11 +loom_version=1.15-SNAPSHOT +base_version=26.1 unstable=false -supported_version=1.21.11 -minecraft_version=1.21.11 +supported_version=26.1 +minecraft_version=26.1 platforms=fabric,neoforge forge_version=49.1.10 -neoforge_version=21.11.8-beta +neoforge_version=26.1.1.15-beta neoforge_pr= -fabricloader_version=0.18.2 -cloth_config_version=21.11.151 +fabricloader_version=0.18.5 +cloth_config_version=26.1.154 modmenu_version=17.0.0-alpha.1 -fabric_api=0.139.5+1.21.11 +fabric_api=0.144.3+26.1 architectury_version=19.0.1 api_exculde= #api_include=me.shedaniel.cloth:cloth-events,me.shedaniel.cloth:config-2,me.sargunvohra.mcmods:autoconfig1u,org.jetbrains:annotations,net.fabricmc.fabric-api:fabric diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 88e30aee6..04166276d 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Sun Dec 30 21:50:18 HKT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip diff --git a/neoforge/build.gradle b/neoforge/build.gradle index 1571c1002..c6be33b13 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -1,245 +1,139 @@ plugins { - id "com.github.johnrengelman.shadow" version "8.1.1" + id("dev.architectury.loom-no-remap") + id("maven-publish") } -architectury { - platformSetupLoomIde() - neoForge() -} - -configurations { - common - shadowCommon // Don't use shadow from the shadow plugin because we don't want IDEA to index this. - compileClasspath.extendsFrom common - runtimeClasspath.extendsFrom common -} +version = "26.1.1.15-beta" +group = "me.shedaniel" -processResources { - filesMatching("META-INF/neoforge.mods.toml") { - expand "version": project.version - } - inputs.property "version", project.version +base { + archivesName = "RoughlyEnoughItems-neoforge" } repositories { - maven { url "https://www.cursemaven.com" } - maven { url "https://dl.cloudsmith.io/public/geckolib3/geckolib/maven" } - maven { - name "Modmaven" - url "https://modmaven.dev/" - // For Gradle 5.1 and above, limit it to just AE2 - content { - includeGroup 'appeng' - } - } + maven { url "https://maven.shedaniel.me" } + maven { url "https://maven.architectury.dev" } + maven { url "https://maven.terraformersmc.com/releases" } + maven { url "https://maven.neoforged.net/releases/" } } sourceSets { - serverComponent { - compileClasspath += main.compileClasspath - runtimeClasspath += main.runtimeClasspath + main { + java.srcDirs = [ + layout.buildDirectory.dir("generated/neoforgeSources/main/java") + ] + resources.srcDirs = [ + "../runtime/src/main/resources", + "../neoforge/src/main/resources" + ] + } + test { + java.srcDirs = [ + "../runtime/src/test/java" + ] } } -processServerComponentResources { - filesMatching("META-INF/neoforge.mods.toml") { - expand "version": project.version +def prepareNeoForgeSources = tasks.register("prepareNeoForgeSources", Sync) { + into layout.buildDirectory.dir("generated/neoforgeSources/main/java") + outputs.upToDateWhen { false } + + from("../api/src/main/java") + from("../runtime/src/main/java") { + exclude("me/shedaniel/rei/impl/client/registry/screen/DefaultScreenOverlayRenderer.java") + } + from("../default-plugin/src/main/java") + from("../neoforge/src/main/java") + + exclude("me/shedaniel/rei/REIModMenuEntryPoint.java") + exclude("me/shedaniel/rei/mixin/forge/MixinEffectsInInventory.java") + + filter { String line -> + line = line.replaceAll(/\bGuiGraphics\b/, "GuiGraphicsExtractor") + line = line + .replace(".drawCenteredString(", ".centeredText(") + .replace(".drawWordWrap(", ".textWithWordWrap(") + .replace(".drawString(", ".text(") + .replace(".renderOutline(", ".outline(") + .replace(".hLine(", ".horizontalLine(") + .replace(".vLine(", ".verticalLine(") + .replace(".renderItemDecorations(", ".itemDecorations(") + .replace(".renderItem(", ".item(") + .replace("graphics.renderDeferredElements();", "graphics.extractDeferredElements(mouseX, mouseY, delta);") + .replace("graphics instanceof GuiGraphicsExtractor guiGraphics ? guiGraphics : new GuiGraphicsExtractor(graphics)", "graphics") + .replace("graphics instanceof GuiGraphicsExtractor existing ? existing : new GuiGraphicsExtractor(graphics)", "graphics") + .replace('initializeEntryPoint(true, "me.shedaniel.rei.REIModMenuEntryPoint");', "") + line = line.replaceAll(/(\w+)\.withFreshScissorStack\(/, 'me.shedaniel.rei.impl.client.gui.forge.REIGuiGraphicsCompat.withFreshScissorStack($1, ') + line = line.replaceAll(/(\w+)\.innerBlit\(/, 'me.shedaniel.rei.impl.client.gui.forge.REIGuiGraphicsCompat.innerBlit($1, ') + return line } - inputs.property "version", project.version } loom { -} + noIntermediateMappings() -def depProjects = [":api", ":runtime", ":default-plugin"] + mods { + roughlyenoughitems { + sourceSet sourceSets.main + } + } +} dependencies { - neoForge("net.neoforged:neoforge:${rootProject.neoforge_version}") - modApi("me.shedaniel.cloth:cloth-config-neoforge:${cloth_config_version}") - modApi("dev.architectury:architectury-neoforge:${architectury_version}") + minecraft("com.mojang:minecraft:${project.minecraft_version}") - depProjects.forEach { - common(project(path: it, configuration: "namedElements")) { transitive false } - shadowCommon(project(path: it, configuration: "transformProductionNeoForge")) { transitive false } - } -} + neoForge("net.neoforged:neoforge:${project.neoforge_version}") + compileOnly("net.fabricmc:fabric-loader:${project.fabricloader_version}") + implementation("me.shedaniel.cloth:cloth-config-neoforge:${cloth_config_version}") + implementation("dev.architectury:architectury-neoforge:${architectury_version}") -def modRuntime(str) { - dependencies.modLocalRuntime(str) + testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } -shadowJar { - configurations = [project.configurations.shadowCommon] - archiveClassifier = "dev-shadow" -} +processResources { + inputs.property "version", project.version -remapJar { - input.set shadowJar.archiveFile - dependsOn shadowJar - archiveClassifier = null + filesMatching("META-INF/neoforge.mods.toml") { + expand "version": project.version + } } -task renameJarForPublication(type: Zip, dependsOn: remapJar) { - from remapJar.archiveFile.map { zipTree(it) } - metadataCharset "UTF-8" - archiveExtension = "jar" - destinationDirectory = base.libsDirectory - archiveClassifier = project.name +tasks.withType(JavaCompile).configureEach { + dependsOn prepareNeoForgeSources + options.encoding = "UTF-8" + options.release = 25 + options.compilerArgs += ["-Xmaxerrs", "120"] } -assemble.dependsOn renameJarForPublication - -jar { - archiveClassifier = "dev" +test { + useJUnitPlatform() } java { withSourcesJar() + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 } -sourcesJar { - afterEvaluate { - depProjects.forEach { - def depSources = project(it).sourcesJar - dependsOn depSources - from depSources.archiveFile.map { zipTree(it) } - } - } +tasks.named("sourcesJar") { + dependsOn prepareNeoForgeSources } -task serverOnlyJar(type: Jar, dependsOn: [remapJar]) { - archiveClassifier = "server-only" - from(zipTree(remapJar.archiveFile.get().asFile)) { - exclude "META-INF/neoforge.mods.toml", "mezz/**", "me/shedaniel/rei/forge/RoughlyEnoughItemsForge.class", "me/shedaniel/rei/forge/JEIStub.class" - } - from(sourceSets.serverComponent.output) -} - -tasks.build { - dependsOn tasks.serverOnlyJar -} +jar { + exclude("net/**") -components.java { - withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { - skip() + from("../LICENSE") { + rename { "${it}_${project.base.archivesName.get()}" } } } publishing { publications { - mavenNeoForge(MavenPublication) { - artifactId = rootProject.name + "-" + project.name + create("mavenJava", MavenPublication) { + artifactId = project.base.archivesName.get() from components.java } - ["api", "default-plugin"].forEach { projectName -> - def remapMojang = tasks.create("remapMojangJarFor$projectName", net.fabricmc.loom.task.RemapJarTask) { - inputFile = project(":" + projectName).tasks.jar.archiveFile - archiveClassifier = "mojang-remapped-$projectName" - targetNamespace = "mojang" - } - def remapMojangSourcesJar = tasks.create("remapMojangSourcesFor$projectName", net.fabricmc.loom.task.RemapSourcesJarTask) { - inputFile = project(":" + projectName).tasks.sourcesJar.archiveFile - archiveClassifier = "mojang-remapped-$projectName-sources" - targetNamespace = "mojang" - } - create(projectName + "NeoForge", MavenPublication.class) { publication -> - publication.artifactId = rootProject.name + "-" + projectName + "-neoforge" - project.afterEvaluate { - def project = project(":" + projectName) - remapMojang.inputFile = project.fakeForgeJar.archiveFile - def normalArtifact, sourceArtifact - publication.artifact(remapMojang) { classifier null; normalArtifact = it } - publication.artifact(remapMojangSourcesJar) { - builtBy remapMojangSourcesJar - classifier "sources" - sourceArtifact = it - } - - from components.java - publication.setArtifacts([normalArtifact, sourceArtifact]) - } - } - } - } -} - -unifiedPublishing { - if (rootProject.neoforge_pr != "") return - project { - displayName = "[NeoForge $rootProject.supported_version] v$project.version" - releaseType = "beta" - gameVersions = ["1.21.11"] - gameLoaders = ["neoforge"] - changelog = rootProject.releaseChangelog - - mainPublication renameJarForPublication - - relations { - depends { - curseforge = "architectury-api" - modrinth = "architectury-api" - } - depends { - curseforge = "cloth-config" - modrinth = "cloth-config" - } - } - - if (project.hasProperty("danielshe_curse_api_key") || System.getenv("danielshe_curse_api_key") != null) { - curseforge { - token = project.hasProperty("danielshe_curse_api_key") ? project.property("danielshe_curse_api_key") : System.getenv("danielshe_curse_api_key") - id = "310111" - gameVersions.addAll "Java 17" - - relations { - depends "roughly-enough-items-hacks" - } - } - } - - if (project.hasProperty("modrinth_key") || System.getenv("modrinth_key") != null) { - modrinth { - token = project.hasProperty("modrinth_key") ? project.property("modrinth_key") : System.getenv("modrinth_key") - id = "nfn13YXA" - version = "$project.version+$project.name" - } - } - } - project { - displayName = "[NeoForge $rootProject.supported_version] v$project.version" - releaseType = "release" - gameVersions = ["1.21.11"] - gameLoaders = ["neoforge"] - changelog = rootProject.releaseChangelog - - mainPublication serverOnlyJar - - relations { - depends { - curseforge = "architectury-api" - modrinth = "architectury-api" - } - depends { - curseforge = "cloth-config" - modrinth = "cloth-config" - } - } - - if (project.hasProperty("danielshe_curse_api_key") || System.getenv("danielshe_curse_api_key") != null) { - curseforge { - token = project.hasProperty("danielshe_curse_api_key") ? project.property("danielshe_curse_api_key") : System.getenv("danielshe_curse_api_key") - id = "567899" - gameVersions.addAll "Java 17" - } - } - - if (project.hasProperty("modrinth_key") || System.getenv("modrinth_key") != null) { - modrinth { - releaseType = "release" - token = project.hasProperty("modrinth_key") ? project.property("modrinth_key") : System.getenv("modrinth_key") - id = "OM4ZYSws" - version = "$project.version+$project.name" - } - } } } diff --git a/neoforge/gradle.properties b/neoforge/gradle.properties index 7da18ea6f..8ae0455bb 100644 --- a/neoforge/gradle.properties +++ b/neoforge/gradle.properties @@ -1 +1,2 @@ loom.platform=neoforge +minecraft_version=26.1.1 diff --git a/neoforge/settings.gradle b/neoforge/settings.gradle new file mode 100644 index 000000000..582bcd9ab --- /dev/null +++ b/neoforge/settings.gradle @@ -0,0 +1,38 @@ +pluginManagement { + def rootProperties = new Properties() + file("../gradle.properties").withInputStream { + rootProperties.load(it) + } + + repositories { + maven { url "https://maven.fabricmc.net/" } + maven { url "https://maven.shedaniel.me/" } + maven { url "https://maven.architectury.dev/" } + maven { url "https://maven.minecraftforge.net/" } + maven { url "https://files.minecraftforge.net/maven/" } + gradlePluginPortal() + } + + plugins { + id("dev.architectury.loom-no-remap") version "1.14-SNAPSHOT" + } +} + +def rootProperties = new Properties() +file("../gradle.properties").withInputStream { + rootProperties.load(it) +} + +gradle.beforeProject { project -> + rootProperties.each { key, value -> + if (!project.hasProperty(key.toString())) { + project.ext.set(key.toString(), value) + } + } +} + +if (JavaVersion.current().ordinal() + 1 < 25) { + throw new IllegalStateException("Please run gradle with Java 25+!") +} + +rootProject.name = "RoughlyEnoughItems-neoforge" diff --git a/neoforge/src/main/java/me/shedaniel/rei/forge/RoughlyEnoughItemsForge.java b/neoforge/src/main/java/me/shedaniel/rei/forge/RoughlyEnoughItemsForge.java index b95638f63..71c88250a 100644 --- a/neoforge/src/main/java/me/shedaniel/rei/forge/RoughlyEnoughItemsForge.java +++ b/neoforge/src/main/java/me/shedaniel/rei/forge/RoughlyEnoughItemsForge.java @@ -23,6 +23,7 @@ package me.shedaniel.rei.forge; +import me.shedaniel.rei.impl.client.forge.MobEffectLayoutHandlerImpl; import me.shedaniel.rei.impl.init.RoughlyEnoughItemsInitializer; import net.neoforged.api.distmarker.Dist; import net.neoforged.fml.common.Mod; @@ -40,6 +41,7 @@ public RoughlyEnoughItemsForge() { RoughlyEnoughItemsInitializer.onInitialize(); if (FMLEnvironment.getDist() == Dist.CLIENT) { + MobEffectLayoutHandlerImpl.register(); run(() -> RoughlyEnoughItemsInitializer::onInitializeClient); } } diff --git a/neoforge/src/main/java/me/shedaniel/rei/impl/client/forge/CreativeModeTabCollectorImpl.java b/neoforge/src/main/java/me/shedaniel/rei/impl/client/forge/CreativeModeTabCollectorImpl.java index 57b097ec6..a41b2c179 100644 --- a/neoforge/src/main/java/me/shedaniel/rei/impl/client/forge/CreativeModeTabCollectorImpl.java +++ b/neoforge/src/main/java/me/shedaniel/rei/impl/client/forge/CreativeModeTabCollectorImpl.java @@ -25,8 +25,6 @@ import me.shedaniel.rei.api.common.display.basic.BasicDisplay; import me.shedaniel.rei.impl.common.InternalLogger; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.ResourceKey; import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.item.CreativeModeTab; @@ -48,10 +46,7 @@ public static Map> collectTabs() { if (tab.getType() != CreativeModeTab.Type.HOTBAR && tab.getType() != CreativeModeTab.Type.INVENTORY) { try { CreativeModeTab.ItemDisplayBuilder builder = new CreativeModeTab.ItemDisplayBuilder(tab, featureFlags); - ResourceKey resourceKey = BuiltInRegistries.CREATIVE_MODE_TAB - .getResourceKey(tab) - .orElseThrow(() -> new IllegalStateException("Unregistered creative tab: " + tab)); - EventHooks.onCreativeModeTabBuildContents(tab, resourceKey, tab.displayItemsGenerator, parameters, (stack, visibility) -> { + EventHooks.onCreativeModeTabBuildContents(tab, tab.displayItemsGenerator, parameters, (stack, visibility) -> { if (visibility == CreativeModeTab.TabVisibility.SEARCH_TAB_ONLY) return; builder.accept(stack, visibility); }); diff --git a/neoforge/src/main/java/me/shedaniel/rei/impl/client/forge/MobEffectLayoutHandlerImpl.java b/neoforge/src/main/java/me/shedaniel/rei/impl/client/forge/MobEffectLayoutHandlerImpl.java new file mode 100644 index 000000000..2938b8d13 --- /dev/null +++ b/neoforge/src/main/java/me/shedaniel/rei/impl/client/forge/MobEffectLayoutHandlerImpl.java @@ -0,0 +1,56 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.forge; + +import me.shedaniel.rei.api.client.config.ConfigObject; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.neoforged.neoforge.client.event.ScreenEvent; +import net.neoforged.neoforge.common.NeoForge; + +public final class MobEffectLayoutHandlerImpl { + private MobEffectLayoutHandlerImpl() { + } + + public static void register() { + NeoForge.EVENT_BUS.addListener(MobEffectLayoutHandlerImpl::onRenderInventoryMobEffects); + } + + private static void onRenderInventoryMobEffects(ScreenEvent.RenderInventoryMobEffects event) { + if (!ConfigObject.getInstance().isLeftSideMobEffects()) { + return; + } + + Screen screen = event.getScreen(); + if (!(screen instanceof AbstractContainerScreen containerScreen)) { + return; + } + + int left = containerScreen.leftPos; + boolean wide = left >= 120; + + event.setCompact(!wide); + event.setHorizontalOffset(wide ? left - 120 - 4 : left - 32 - 4); + } +} diff --git a/neoforge/src/main/java/me/shedaniel/rei/impl/client/gui/forge/REIGuiGraphicsCompat.java b/neoforge/src/main/java/me/shedaniel/rei/impl/client/gui/forge/REIGuiGraphicsCompat.java new file mode 100644 index 000000000..4a796fa78 --- /dev/null +++ b/neoforge/src/main/java/me/shedaniel/rei/impl/client/gui/forge/REIGuiGraphicsCompat.java @@ -0,0 +1,109 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.gui.forge; + +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.textures.GpuSampler; +import com.mojang.blaze3d.textures.GpuTextureView; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.resources.Identifier; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public final class REIGuiGraphicsCompat { + private static final Field SCISSOR_STACK_FIELD = findField("scissorStack"); + private static final Method INNER_TEXTURED_BLIT_METHOD = findMethod("innerBlit", + RenderPipeline.class, GpuTextureView.class, GpuSampler.class, + int.class, int.class, int.class, int.class, + float.class, float.class, float.class, float.class, int.class); + + private REIGuiGraphicsCompat() { + } + + public static void innerBlit(GuiGraphicsExtractor graphics, RenderPipeline pipeline, Identifier location, + int xStart, int xEnd, int yStart, int yEnd, + float u0, float u1, float v0, float v1, int color) { + AbstractTexture texture = Minecraft.getInstance().getTextureManager().getTexture(location); + invoke(INNER_TEXTURED_BLIT_METHOD, graphics, pipeline, texture.getTextureView(), texture.getSampler(), + xStart, yStart, xEnd, yEnd, u0, u1, v0, v1, color); + } + + public static void withFreshScissorStack(GuiGraphicsExtractor graphics, Runnable runnable) { + Object previous = readField(SCISSOR_STACK_FIELD, graphics); + try { + Constructor constructor = SCISSOR_STACK_FIELD.getType().getDeclaredConstructor(); + constructor.setAccessible(true); + SCISSOR_STACK_FIELD.set(graphics, constructor.newInstance()); + runnable.run(); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to swap GuiGraphicsExtractor scissor stack", e); + } finally { + try { + SCISSOR_STACK_FIELD.set(graphics, previous); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to restore GuiGraphicsExtractor scissor stack", e); + } + } + } + + private static Field findField(String name) { + try { + Field field = GuiGraphicsExtractor.class.getDeclaredField(name); + field.setAccessible(true); + return field; + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to resolve GuiGraphicsExtractor field: " + name, e); + } + } + + private static Method findMethod(String name, Class... parameterTypes) { + try { + Method method = GuiGraphicsExtractor.class.getDeclaredMethod(name, parameterTypes); + method.setAccessible(true); + return method; + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to resolve GuiGraphicsExtractor method: " + name, e); + } + } + + private static Object readField(Field field, Object instance) { + try { + return field.get(instance); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to read GuiGraphicsExtractor field: " + field.getName(), e); + } + } + + private static void invoke(Method method, Object instance, Object... args) { + try { + method.invoke(instance, args); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to invoke GuiGraphicsExtractor method: " + method.getName(), e); + } + } +} diff --git a/neoforge/src/main/java/me/shedaniel/rei/impl/client/gui/forge/ScreenOverlayImplForge.java b/neoforge/src/main/java/me/shedaniel/rei/impl/client/gui/forge/ScreenOverlayImplForge.java index dea238b6d..0c1748569 100644 --- a/neoforge/src/main/java/me/shedaniel/rei/impl/client/gui/forge/ScreenOverlayImplForge.java +++ b/neoforge/src/main/java/me/shedaniel/rei/impl/client/gui/forge/ScreenOverlayImplForge.java @@ -68,9 +68,7 @@ public void renderTooltipInner(Screen screen, GuiGraphics graphics, Tooltip tool if (!itemStack.isEmpty()) { font = ClientHooks.getTooltipFont(itemStack, font); } - graphics.tooltipStack = itemStack; - graphics.setTooltipForNextFrameInternal(font, components, mouseX, mouseY, DefaultTooltipPositioner.INSTANCE, tooltip.getTooltipStyle(), false); - graphics.tooltipStack = ItemStack.EMPTY; + graphics.tooltip(font, components, mouseX, mouseY, DefaultTooltipPositioner.INSTANCE, tooltip.getTooltipStyle()); graphics.pose().popMatrix(); } } diff --git a/neoforge/src/main/java/me/shedaniel/rei/impl/client/registry/screen/DefaultScreenOverlayRenderer.java b/neoforge/src/main/java/me/shedaniel/rei/impl/client/registry/screen/DefaultScreenOverlayRenderer.java new file mode 100644 index 000000000..88834cebe --- /dev/null +++ b/neoforge/src/main/java/me/shedaniel/rei/impl/client/registry/screen/DefaultScreenOverlayRenderer.java @@ -0,0 +1,115 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.registry.screen; + +import me.shedaniel.rei.api.client.gui.screen.DisplayScreen; +import me.shedaniel.rei.api.client.registry.screen.OverlayRendererProvider; +import me.shedaniel.rei.impl.common.InternalLogger; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.neoforged.neoforge.client.event.ContainerScreenEvent; +import net.neoforged.neoforge.client.event.ScreenEvent; +import net.neoforged.neoforge.common.NeoForge; +import org.jetbrains.annotations.Nullable; + +import static me.shedaniel.rei.RoughlyEnoughItemsCoreClient.resetFocused; +import static me.shedaniel.rei.RoughlyEnoughItemsCoreClient.shouldReturn; + +public enum DefaultScreenOverlayRenderer implements OverlayRendererProvider { + INSTANCE; + + @Nullable + private Sink sink; + private int rendered; + + DefaultScreenOverlayRenderer() { + NeoForge.EVENT_BUS.addListener(this::onScreenRenderPre); + NeoForge.EVENT_BUS.addListener(this::onScreenRenderBackground); + NeoForge.EVENT_BUS.addListener(this::onContainerScreenRenderForeground); + NeoForge.EVENT_BUS.addListener(this::onScreenRenderPost); + } + + private void onScreenRenderPre(ScreenEvent.Render.Pre event) { + if (!shouldReturn(event.getScreen())) { + rendered = 0; + } + } + + private void onScreenRenderBackground(ScreenEvent.Render.Background event) { + Screen screen = event.getScreen(); + Sink sink = this.sink; + if (sink == null || shouldReturn(screen) || !(screen instanceof AbstractContainerScreen) || screen instanceof DisplayScreen) { + return; + } + + rendered = 1; + resetFocused(screen); + sink.render(event.getGuiGraphics(), event.getMouseX(), event.getMouseY(), event.getPartialTick()); + resetFocused(screen); + } + + private void onContainerScreenRenderForeground(ContainerScreenEvent.Render.Foreground event) { + Screen screen = event.getContainerScreen(); + if (!shouldReturn(screen)) { + rendered = 2; + resetFocused(screen); + } + } + + private void onScreenRenderPost(ScreenEvent.Render.Post event) { + Screen screen = event.getScreen(); + Sink sink = this.sink; + if (sink == null || shouldReturn(screen)) { + return; + } + + GuiGraphicsExtractor graphics = event.getGuiGraphics(); + int mouseX = event.getMouseX(); + int mouseY = event.getMouseY(); + float delta = event.getPartialTick(); + + if (screen instanceof AbstractContainerScreen && rendered < 2) { + InternalLogger.getInstance().warn("Screen " + screen.getClass().getName() + " did not render background and foreground! This might cause rendering issues!"); + } + + resetFocused(screen); + if (rendered == 0 && !(screen instanceof DisplayScreen) && (!(screen instanceof AbstractContainerScreen) || rendered < 2)) { + sink.render(graphics, mouseX, mouseY, delta); + } + rendered = 1; + sink.lateRender(graphics, mouseX, mouseY, delta); + resetFocused(screen); + } + + @Override + public void onApplied(Sink sink) { + this.sink = sink; + } + + @Override + public void onRemoved() { + this.sink = null; + } +} diff --git a/neoforge/src/main/java/me/shedaniel/rei/mixin/forge/MixinClientPacketListener.java b/neoforge/src/main/java/me/shedaniel/rei/mixin/forge/MixinClientPacketListener.java index 3dfc3fdc5..a2c6f5a6d 100644 --- a/neoforge/src/main/java/me/shedaniel/rei/mixin/forge/MixinClientPacketListener.java +++ b/neoforge/src/main/java/me/shedaniel/rei/mixin/forge/MixinClientPacketListener.java @@ -44,10 +44,11 @@ public abstract class MixinClientPacketListener { @Inject(method = "handleUpdateRecipes", at = @At("HEAD")) private void handleUpdateRecipes(ClientboundUpdateRecipesPacket clientboundUpdateRecipesPacket, CallbackInfo ci) { RoughlyEnoughItemsCoreClient.PRE_UPDATE_RECIPES.invoker().accept(recipes(), registryAccess()); + RoughlyEnoughItemsCoreClient.handleClientRecipeDefinitionsUpdated(); } @Inject(method = "handleUpdateTags", at = @At("HEAD")) private void handleUpdateTags(ClientboundUpdateTagsPacket packet, CallbackInfo ci) { RoughlyEnoughItemsCoreClient.POST_UPDATE_TAGS.invoker().run(); } -} \ No newline at end of file +} diff --git a/neoforge/src/main/java/me/shedaniel/rei/mixin/forge/MixinClientRecipeBook.java b/neoforge/src/main/java/me/shedaniel/rei/mixin/forge/MixinClientRecipeBook.java new file mode 100644 index 000000000..49ef31449 --- /dev/null +++ b/neoforge/src/main/java/me/shedaniel/rei/mixin/forge/MixinClientRecipeBook.java @@ -0,0 +1,40 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.mixin.forge; + +import me.shedaniel.rei.impl.client.access.ClientRecipeBookEntriesAccessor; +import net.minecraft.client.ClientRecipeBook; +import net.minecraft.world.item.crafting.display.RecipeDisplayEntry; +import net.minecraft.world.item.crafting.display.RecipeDisplayId; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(ClientRecipeBook.class) +public interface MixinClientRecipeBook extends ClientRecipeBookEntriesAccessor { + @Override + @Accessor("known") + Map rei$getKnownEntries(); +} diff --git a/neoforge/src/main/resources/META-INF/accesstransformer.cfg b/neoforge/src/main/resources/META-INF/accesstransformer.cfg index 452d1be73..5f45133c9 100644 --- a/neoforge/src/main/resources/META-INF/accesstransformer.cfg +++ b/neoforge/src/main/resources/META-INF/accesstransformer.cfg @@ -52,3 +52,5 @@ public-f net.minecraft.client.gui.GuiGraphics scissorStack public net.minecraft.client.gui.GuiGraphics$ScissorStack public net.minecraft.client.gui.GuiGraphics$ScissorStack stack public net.minecraft.client.gui.GuiGraphics$ScissorStack ()V +public net.minecraft.client.gui.screens.Screen defaultHandleGameClickEvent(Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/client/Minecraft;Lnet/minecraft/client/gui/screens/Screen;)V +public net.minecraft.client.StringSplitter$WidthLimitedCharSink diff --git a/neoforge/src/main/resources/rei.mixins.json b/neoforge/src/main/resources/rei.mixins.json index 5551edf4e..7d574e8ef 100644 --- a/neoforge/src/main/resources/rei.mixins.json +++ b/neoforge/src/main/resources/rei.mixins.json @@ -4,7 +4,7 @@ "compatibilityLevel": "JAVA_8", "client": [ "MixinClientPacketListener", - "MixinEffectsInInventory", + "MixinClientRecipeBook", "MixinInputConstants", "MixinInputConstantsKey", "MixinRecipeToast" diff --git a/runtime/build.gradle b/runtime/build.gradle index 3061e2c05..a02468cfd 100644 --- a/runtime/build.gradle +++ b/runtime/build.gradle @@ -1,4 +1,6 @@ -archivesBaseName = rootProject.name + "-" + project.name +base { + archivesName = rootProject.name + "-" + project.name +} architectury { common(platforms.split(",")) @@ -9,9 +11,9 @@ loom { } dependencies { - modCompileOnly("net.fabricmc:fabric-loader:${project.fabricloader_version}") - modApi("me.shedaniel.cloth:cloth-config:${cloth_config_version}") - modApi("dev.architectury:architectury:${architectury_version}") + compileOnly("net.fabricmc:fabric-loader:${project.fabricloader_version}") + api("me.shedaniel.cloth:cloth-config:${cloth_config_version}") + api("dev.architectury:architectury:${architectury_version}") compileOnly(annotationProcessor("org.projectlombok:lombok:1.18.22")) compileOnly(project(path: ":api", configuration: "namedElements")) testImplementation(project(path: ":api", configuration: "namedElements")) diff --git a/runtime/src/main/java/me/shedaniel/rei/REIModMenuEntryPoint.java b/runtime/src/main/java/me/shedaniel/rei/REIModMenuEntryPoint.java index 19eac34bb..f0d9e5aea 100644 --- a/runtime/src/main/java/me/shedaniel/rei/REIModMenuEntryPoint.java +++ b/runtime/src/main/java/me/shedaniel/rei/REIModMenuEntryPoint.java @@ -24,11 +24,11 @@ package me.shedaniel.rei; import dev.architectury.platform.Platform; -import dev.architectury.platform.client.ConfigurationScreenRegistry; +import dev.architectury.platform.client.fabric.ConfigurationScreenRegistryImpl; import me.shedaniel.rei.api.client.config.ConfigManager; public class REIModMenuEntryPoint { public void onInitializeClient() { - ConfigurationScreenRegistry.register(Platform.getMod("roughlyenoughitems"), ConfigManager.getInstance()::getConfigScreen); + ConfigurationScreenRegistryImpl.register(Platform.getMod("roughlyenoughitems"), ConfigManager.getInstance()::getConfigScreen); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java index d2c79d22b..2d62c1cef 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java @@ -69,6 +69,7 @@ import org.jetbrains.annotations.ApiStatus; import java.util.Comparator; +import java.util.Optional; import java.util.ServiceLoader; import java.util.function.Consumer; import java.util.function.Function; @@ -85,13 +86,13 @@ public class RoughlyEnoughItemsCore { logger.info("Minecraft: " + adapter.getMinecraftVersion()); logger.info("Side: " + (adapter.isClient() ? "client" : "server")); logger.info("Development: " + adapter.isDev()); - logger.info("Version: " + Platform.getOptionalMod("roughlyenoughitems").map(Mod::getVersion).orElse(null)); + logger.info("Version: " + getOptionalMod("roughlyenoughitems").map(Mod::getVersion).orElse(null)); logger.info("Loader:"); - logger.info("- " + (fabric ? "Fabric" : "Forge") + ": " + Platform.getOptionalMod(fabric ? "fabricloader" : "forge").map(Mod::getVersion).orElse(null)); - if (fabric) logger.info("- Fabric API: " + Platform.getOptionalMod("fabric").map(Mod::getVersion).orElse(null)); + logger.info("- " + (fabric ? "Fabric" : "Forge") + ": " + getOptionalMod(fabric ? "fabricloader" : "forge").map(Mod::getVersion).orElse(null)); + if (fabric) logger.info("- Fabric API: " + getOptionalMod("fabric-api", "fabric").map(Mod::getVersion).orElse(null)); logger.info("Dependencies:"); - logger.info("- Cloth Config: " + Platform.getOptionalMod(fabric ? "cloth-config2" : "cloth_config").map(Mod::getVersion).orElse(null)); - logger.info("- Architectury: " + Platform.getOptionalMod("architectury").map(Mod::getVersion).orElse(null)); + logger.info("- Cloth Config: " + getOptionalMod(fabric ? "cloth-config" : "cloth_config", fabric ? "cloth-config2" : "cloth_config").map(Mod::getVersion).orElse(null)); + logger.info("- Architectury: " + getOptionalMod("architectury").map(Mod::getVersion).orElse(null)); String mixin = "null"; try { mixin = (String) Class.forName("org.spongepowered.asm.launch.MixinBootstrap").getDeclaredField("VERSION").get(null); @@ -117,6 +118,17 @@ private static T make(T object, Consumer consumer) { consumer.accept(object); return object; } + + private static Optional getOptionalMod(String... ids) { + for (String id : ids) { + try { + return Platform.getOptionalMod(id); + } catch (Throwable ignored) { + } + } + + return Optional.empty(); + } static { attachCommonInternals(); diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java index 721cd88ac..7fd870ced 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java @@ -54,13 +54,16 @@ import me.shedaniel.rei.api.client.registry.screen.OverlayDecider; import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry; import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.api.common.plugins.PluginManager; import me.shedaniel.rei.api.common.plugins.PluginView; import me.shedaniel.rei.api.common.plugins.REICommonPlugin; import me.shedaniel.rei.api.common.registry.ReloadStage; import me.shedaniel.rei.api.common.util.CollectionUtils; +import me.shedaniel.rei.api.common.util.EntryIngredients; import me.shedaniel.rei.api.common.util.EntryStacks; import me.shedaniel.rei.impl.ClientInternals; +import me.shedaniel.rei.impl.client.access.ClientRecipeBookEntriesAccessor; import me.shedaniel.rei.impl.client.ClientHelperImpl; import me.shedaniel.rei.impl.client.REIRuntimeImpl; import me.shedaniel.rei.impl.client.config.ConfigManagerImpl; @@ -75,6 +78,7 @@ import me.shedaniel.rei.impl.client.gui.widget.QueuedTooltip; import me.shedaniel.rei.impl.client.gui.widget.TooltipContextImpl; import me.shedaniel.rei.impl.client.gui.widget.search.OverlaySearchField; +import me.shedaniel.rei.impl.client.recipe.ClientLocalRecipeManager; import me.shedaniel.rei.impl.client.registry.category.CategoryRegistryImpl; import me.shedaniel.rei.impl.client.registry.display.DisplayRegistryImpl; import me.shedaniel.rei.impl.client.registry.screen.ScreenRegistryImpl; @@ -94,17 +98,23 @@ import me.shedaniel.rei.impl.common.plugins.ReloadManagerImpl; import me.shedaniel.rei.impl.common.util.InstanceHelper; import me.shedaniel.rei.impl.common.util.IssuesDetector; +import me.shedaniel.rei.plugin.common.displays.cooking.DefaultBlastingDisplay; +import me.shedaniel.rei.plugin.common.displays.cooking.DefaultSmeltingDisplay; +import me.shedaniel.rei.plugin.common.displays.cooking.DefaultSmokingDisplay; +import me.shedaniel.rei.plugin.common.displays.crafting.DefaultCraftingDisplay; import me.shedaniel.rei.plugin.test.REITestCommonPlugin; import me.shedaniel.rei.plugin.test.REITestPlugin; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; +import net.minecraft.client.ClientRecipeBook; import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.components.ImageButton; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.CraftingScreen; +import net.minecraft.client.gui.screens.recipebook.RecipeCollection; import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent; import net.minecraft.client.resources.language.I18n; import net.minecraft.core.RegistryAccess; @@ -117,8 +127,18 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.crafting.RecipeAccess; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.item.crafting.display.FurnaceRecipeDisplay; +import net.minecraft.world.item.crafting.display.RecipeDisplay; import net.minecraft.world.item.crafting.display.RecipeDisplayEntry; import net.minecraft.world.item.crafting.display.RecipeDisplayId; +import net.minecraft.world.item.crafting.display.ShapedCraftingRecipeDisplay; +import net.minecraft.world.item.crafting.display.ShapelessCraftingRecipeDisplay; +import net.minecraft.world.item.crafting.display.SmithingRecipeDisplay; +import net.minecraft.world.item.crafting.display.StonecutterRecipeDisplay; +import net.minecraft.world.item.Items; import org.apache.commons.lang3.mutable.MutableLong; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -131,6 +151,18 @@ public class RoughlyEnoughItemsCoreClient { public static final Event> PRE_UPDATE_RECIPES = EventFactory.createLoop(); public static final Event POST_UPDATE_TAGS = EventFactory.createLoop(); + private static boolean receivedServerDisplaySync = false; + private static final MutableLong CLIENT_RECIPE_RELOAD_DEBOUNCE = new MutableLong(-1); + private static final MutableLong CLIENT_RECIPE_SEARCH_SYNC_DEBOUNCE = new MutableLong(-1); + private static @Nullable ClientRecipeFallbackSyncState lastClientRecipeFallbackSyncState; + /** + * Server-known client recipe displays are tracked separately from the player's local recipe manager. + * On vanilla servers and Realms this cache is fed by Architectury's client recipe update events. + */ + private static final Map CLIENT_RECIPE_FALLBACK = new LinkedHashMap<>(); + private static final Comparator> RECIPE_HOLDER_COMPARATOR = Comparator + .comparing((RecipeHolder recipe) -> recipe.id().identifier().getNamespace()) + .thenComparing(recipe -> recipe.id().identifier().getPath()); public static boolean isLeftMousePressed = false; public static void attachClientInternals() { @@ -230,6 +262,7 @@ public Stream> getCategories() { public void onInitializeClient() { IssuesDetector.detect(); + ClientLocalRecipeManager.init(); registerEvents(); RoughlyEnoughItemsCore.getPluginDetector().detectClientPlugins().get().run(); loadTestPlugins(); @@ -239,7 +272,7 @@ public void onInitializeClient() { ItemStack stack = buf.readLenientJsonWithCodec(ItemStack.OPTIONAL_CODEC); String player = buf.readUtf(32767); if (client.player != null) { - client.player.displayClientMessage(Component.literal(I18n.get("text.rei.cheat_items").replaceAll("\\{item_name}", EntryStacks.of(stack.copy()).asFormattedText().getString()).replaceAll("\\{item_count}", stack.copy().getCount() + "").replaceAll("\\{player_name}", player)), false); + client.player.sendSystemMessage(Component.literal(I18n.get("text.rei.cheat_items").replaceAll("\\{item_name}", EntryStacks.of(stack.copy()).asFormattedText().getString()).replaceAll("\\{item_count}", stack.copy().getCount() + "").replaceAll("\\{player_name}", player))); } }); NetworkManager.registerReceiver(NetworkManager.s2c(), RoughlyEnoughItemsNetwork.NOT_ENOUGH_ITEMS_PACKET, (buf, context) -> { @@ -309,36 +342,47 @@ private static boolean _shouldReturn(Screen screen) { private void registerEvents() { Minecraft client = Minecraft.getInstance(); final Identifier recipeButtonTex = Identifier.withDefaultNamespace("textures/gui/recipe_button.png"); - MutableLong endReload = new MutableLong(-1); PRE_UPDATE_RECIPES.register((recipeAccess, registryAccess) -> { + ClientLocalRecipeManager.scheduleReload(); reloadPlugins(null, ReloadStage.START, registryAccess); }); - ClientRecipeUpdateEvent.EVENT.register(recipeManager -> { - reloadPlugins(endReload, ReloadStage.END); + ClientRecipeUpdateEvent.EVENT.register(recipeAccess -> { + ClientLocalRecipeManager.scheduleReload(); + reloadPlugins(CLIENT_RECIPE_RELOAD_DEBOUNCE, ReloadStage.END); }); ClientRecipeUpdateEvent.ADD.register((recipeAccess, entries) -> { if (ClientHelperImpl.getInstance().canUsePackets()) { return; } - - InternalLogger.getInstance().debug("Received server's request to add %d recipes.", entries.size()); - DisplayRegistryImpl registry = (DisplayRegistryImpl) DisplayRegistry.getInstance(); - List mapped = CollectionUtils.map(entries, ClientboundRecipeBookAddPacket.Entry::contents); - registry.addJob(() -> registry.addRecipes(mapped)); + + for (ClientboundRecipeBookAddPacket.Entry entry : entries) { + RecipeDisplayEntry displayEntry = entry.contents(); + CLIENT_RECIPE_FALLBACK.put(displayEntry.id(), displayEntry); + } + + InternalLogger.getInstance().debug("Received server's request to add %d recipes to the client fallback cache.", entries.size()); + queueClientRecipeFallbackSync(); }); ClientRecipeUpdateEvent.REMOVE.register((recipeAccess, entries) -> { if (ClientHelperImpl.getInstance().canUsePackets()) { return; } - - InternalLogger.getInstance().debug("Received server's request to remove %d recipes.", entries.size()); - DisplayRegistryImpl registry = (DisplayRegistryImpl) DisplayRegistry.getInstance(); - Set ids = new HashSet<>(entries); - registry.addJob(() -> registry.removeRecipes(ids)); + + for (RecipeDisplayId id : entries) { + CLIENT_RECIPE_FALLBACK.remove(id); + } + + InternalLogger.getInstance().debug("Received server's request to remove %d recipes from the client fallback cache.", entries.size()); + queueClientRecipeFallbackSync(); }); ClientPlayerEvent.CLIENT_PLAYER_QUIT.register(player -> { InternalLogger.getInstance().debug("Player quit, clearing reload tasks!"); - endReload.setValue(-1); + CLIENT_RECIPE_RELOAD_DEBOUNCE.setValue(-1); + CLIENT_RECIPE_SEARCH_SYNC_DEBOUNCE.setValue(-1); + receivedServerDisplaySync = false; + lastClientRecipeFallbackSyncState = null; + CLIENT_RECIPE_FALLBACK.clear(); + ClientLocalRecipeManager.clear(); ReloadManagerImpl.terminateReloadTasks(); }); ClientGuiEvent.INIT_PRE.register((screen, access) -> { @@ -351,7 +395,7 @@ private void registerEvents() { } InternalLogger.getInstance().error("Detected missing stage: END! This is possibly due to issues during client recipe reload! REI will force a reload of the recipes now!"); - reloadPlugins(endReload, ReloadStage.END); + reloadPlugins(CLIENT_RECIPE_RELOAD_DEBOUNCE, ReloadStage.END); } return EventResult.pass(); @@ -481,6 +525,137 @@ public static boolean resetFocused(Screen screen) { } return true; } + + @ApiStatus.Internal + public static void queueClientRecipeFallbackSync() { + DisplayRegistryImpl registry = (DisplayRegistryImpl) DisplayRegistry.getInstance(); + registry.addJob(() -> { + if (receivedServerDisplaySync) { + lastClientRecipeFallbackSyncState = null; + registry.removeClientFallbackRecipes(); + return; + } + + ClientRecipeFallbackEntries entries = collectClientRecipeFallbackEntries(); + DisplayRegistryImpl.ClientFallbackSyncStats stats = registry.syncClientRecipes(entries.recipeBookEntries(), entries.localEntries()); + lastClientRecipeFallbackSyncState = new ClientRecipeFallbackSyncState(entries.snapshot(), stats.registeredDisplays()); + InternalLogger.getInstance().debug("Queued client fallback sync completed with %d registered displays from %d recipe-book entries and %d local entries.", + stats.registeredDisplays(), entries.recipeBookEntries().size(), entries.localEntries().size()); + }); + } + + @ApiStatus.Internal + public static void handleClientRecipeDefinitionsUpdated() { + receivedServerDisplaySync = false; + lastClientRecipeFallbackSyncState = null; + queueClientRecipeFallbackSync(); + } + + @ApiStatus.Internal + public static void handleClientRecipeBookAdded(int count) { + InternalLogger.getInstance().debug("Observed %d direct client recipe-book additions.", count); + } + + @ApiStatus.Internal + public static void handleClientRecipeBookRemoved(int count) { + InternalLogger.getInstance().debug("Observed %d direct client recipe-book removals.", count); + } + + @ApiStatus.Internal + public static void handleClientRecipeBookRefreshed() { + InternalLogger.getInstance().debug("Observed a direct client recipe-book refresh."); + } + + @ApiStatus.Internal + public static boolean hasReceivedServerDisplaySync() { + return receivedServerDisplaySync; + } + + @ApiStatus.Internal + public static String describeClientRecipeFallbackSyncState() { + ClientRecipeFallbackSyncState state = lastClientRecipeFallbackSyncState; + if (state == null) { + return ""; + } + + ClientRecipeFallbackSnapshot snapshot = state.snapshot(); + return String.format(Locale.ROOT, "recipeBook=%d local=%d registered=%d recipeBookSignature=%d localSignature=%d", + snapshot.recipeBookEntries(), snapshot.localEntries(), state.registeredDisplays(), + snapshot.recipeBookSignature(), snapshot.localSignature()); + } + + @ApiStatus.Internal + public static void ensureClientRecipeFallbackSyncedForSearch() { + if (receivedServerDisplaySync || PluginManager.areAnyReloading()) { + return; + } + + Minecraft client = Minecraft.getInstance(); + if (InstanceHelper.connectionFromClient() == null || client.player == null) { + return; + } + + DisplayRegistryImpl registry = (DisplayRegistryImpl) DisplayRegistry.getInstance(); + boolean missing = !registry.hasClientFallbackRecipes(); + if (!missing) { + return; + } + long now = System.currentTimeMillis(); + if (CLIENT_RECIPE_SEARCH_SYNC_DEBOUNCE.getValue() > 0 + && now - CLIENT_RECIPE_SEARCH_SYNC_DEBOUNCE.getValue() < 250) { + return; + } + CLIENT_RECIPE_SEARCH_SYNC_DEBOUNCE.setValue(now); + + InternalLogger.getInstance().debug("Queueing client fallback refresh for active recipe search because the fallback cache is empty."); + queueClientRecipeFallbackSync(); + } + + private static ClientRecipeFallbackEntries collectClientRecipeFallbackEntries() { + if (InstanceHelper.connectionFromClient() == null) { + return ClientRecipeFallbackEntries.EMPTY; + } + + List recipeBookEntries = List.copyOf(CLIENT_RECIPE_FALLBACK.values()); + RecipeManager localRecipeManager = ClientLocalRecipeManager.getRecipeManager(); + if (localRecipeManager == null) { + return new ClientRecipeFallbackEntries(recipeBookEntries, List.of()); + } + + Set knownIds = new HashSet<>(); + for (RecipeDisplayEntry entry : recipeBookEntries) { + knownIds.add(entry.id()); + } + + Map localEntries = new LinkedHashMap<>(); + localRecipeManager.getRecipes().stream() + .sorted(RECIPE_HOLDER_COMPARATOR) + .forEach(recipe -> localRecipeManager.listDisplaysForRecipe(recipe.id(), entry -> { + if (!knownIds.contains(entry.id())) { + localEntries.putIfAbsent(entry.id(), entry); + } + })); + + return new ClientRecipeFallbackEntries(recipeBookEntries, List.copyOf(localEntries.values())); + } + + public static void markReceivedServerDisplaySync() { + receivedServerDisplaySync = true; + lastClientRecipeFallbackSyncState = null; + DisplayRegistryImpl registry = (DisplayRegistryImpl) DisplayRegistry.getInstance(); + registry.addJob(registry::removeClientFallbackRecipes); + } + + private static boolean isClientRecipeFallbackStale(ClientRecipeFallbackSnapshot snapshot) { + ClientRecipeFallbackSyncState state = lastClientRecipeFallbackSyncState; + if (state == null) { + return true; + } + if (!state.snapshot().equals(snapshot)) { + return true; + } + return state.registeredDisplays() == 0 && snapshot.totalEntries() > 0; + } @ApiStatus.Internal public static void reloadPlugins(MutableLong lastReload, @Nullable ReloadStage start) { @@ -499,4 +674,40 @@ public static void reloadPlugins(MutableLong lastReload, @Nullable ReloadStage s } ReloadManagerImpl.reloadPlugins(start, () -> InstanceHelper.connectionFromClient() == null); } + + private record ClientRecipeFallbackEntries(List recipeBookEntries, List localEntries) { + private static final ClientRecipeFallbackEntries EMPTY = new ClientRecipeFallbackEntries(List.of(), List.of()); + + private ClientRecipeFallbackSnapshot snapshot() { + return new ClientRecipeFallbackSnapshot(recipeBookEntries.size(), localEntries.size(), + signatureOf(recipeBookEntries), signatureOf(localEntries)); + } + } + + private record ClientRecipeFallbackSnapshot(int recipeBookEntries, int localEntries, int recipeBookSignature, int localSignature) { + private int totalEntries() { + return recipeBookEntries + localEntries; + } + } + + private record ClientRecipeFallbackSyncState(ClientRecipeFallbackSnapshot snapshot, int registeredDisplays) { + } + + private static int signatureOf(Collection entries) { + int hash = 1; + var context = EntryIngredients.slotDisplayContext(); + for (RecipeDisplayEntry entry : entries) { + hash = 31 * hash + entry.hashCode(); + hash = 31 * hash + signatureOfResultItems(entry, context); + } + return hash; + } + + private static int signatureOfResultItems(RecipeDisplayEntry entry, net.minecraft.util.context.ContextMap context) { + try { + return entry.resultItems(context).hashCode(); + } catch (Throwable throwable) { + return entry.display().toString().hashCode(); + } + } } diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsNetwork.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsNetwork.java index ee80c5367..a9e7a7968 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsNetwork.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsNetwork.java @@ -77,7 +77,7 @@ public static void onInitialize() { NetworkManager.registerReceiver(NetworkManager.c2s(), DELETE_ITEMS_PACKET, Collections.singletonList(new SplitPacketTransformer()), (buf, context) -> { ServerPlayer player = (ServerPlayer) context.getPlayer(); if (!player.permissions().hasPermission(new Permission.HasCommandLevel(PermissionLevel.GAMEMASTERS))) { - player.displayClientMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED), false); + player.sendSystemMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED)); return; } AbstractContainerMenu menu = player.containerMenu; @@ -90,7 +90,7 @@ public static void onInitialize() { ServerPlayer player = (ServerPlayer) context.getPlayer(); if (!player.permissions().hasPermission(new Permission.HasCommandLevel(PermissionLevel.GAMEMASTERS))) { - player.displayClientMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED), false); + player.sendSystemMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED)); return; } ItemStack stack = buf.readLenientJsonWithCodec(ItemStack.OPTIONAL_CODEC); @@ -100,13 +100,13 @@ public static void onInitialize() { newBuf.writeUtf(player.getScoreboardName(), 32767); NetworkManager.sendToPlayer(player, RoughlyEnoughItemsNetwork.CREATE_ITEMS_MESSAGE_PACKET, newBuf); } else { - player.displayClientMessage(Component.translatable("text.rei.failed_cheat_items"), false); + player.sendSystemMessage(Component.translatable("text.rei.failed_cheat_items")); } }); NetworkManager.registerReceiver(NetworkManager.c2s(), CREATE_ITEMS_GRAB_PACKET, Collections.singletonList(new SplitPacketTransformer()), (buf, context) -> { ServerPlayer player = (ServerPlayer) context.getPlayer(); if (!player.permissions().hasPermission(new Permission.HasCommandLevel(PermissionLevel.GAMEMASTERS))) { - player.displayClientMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED), false); + player.sendSystemMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED)); return; } @@ -128,7 +128,7 @@ public static void onInitialize() { NetworkManager.registerReceiver(NetworkManager.c2s(), CREATE_ITEMS_HOTBAR_PACKET, Collections.singletonList(new SplitPacketTransformer()), (buf, context) -> { ServerPlayer player = (ServerPlayer) context.getPlayer(); if (!player.permissions().hasPermission(new Permission.HasCommandLevel(PermissionLevel.GAMEMASTERS))) { - player.displayClientMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED), false); + player.sendSystemMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED)); return; } ItemStack stack = buf.readLenientJsonWithCodec(ItemStack.OPTIONAL_CODEC); @@ -142,7 +142,7 @@ public static void onInitialize() { newBuf.writeUtf(player.getScoreboardName(), 32767); NetworkManager.sendToPlayer(player, RoughlyEnoughItemsNetwork.CREATE_ITEMS_MESSAGE_PACKET, newBuf); } else { - player.displayClientMessage(Component.translatable("text.rei.failed_cheat_items"), false); + player.sendSystemMessage(Component.translatable("text.rei.failed_cheat_items")); } }); NetworkManager.registerReceiver(NetworkManager.c2s(), MOVE_ITEMS_NEW_PACKET, Collections.singletonList(new SplitPacketTransformer()), (packetByteBuf, context) -> { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/ClientHelperImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/ClientHelperImpl.java index 5decdea57..fd2b692a2 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/ClientHelperImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/ClientHelperImpl.java @@ -235,7 +235,7 @@ public boolean tryCheatingEntry(EntryStack stack) { String madeUpCommand = og.replaceAll("\\{player_name}", Minecraft.getInstance().player.getScoreboardName()).replaceAll("\\{item_name}", identifier.getPath()).replaceAll("\\{item_identifier}", identifier.toString()).replaceAll("\\{nbt}", tagMessage).replaceAll("\\{count}", String.valueOf(cheatedStack.getCount())); if (madeUpCommand.length() > 256) { madeUpCommand = og.replaceAll("\\{player_name}", Minecraft.getInstance().player.getScoreboardName()).replaceAll("\\{item_name}", identifier.getPath()).replaceAll("\\{item_identifier}", identifier.toString()).replaceAll("\\{nbt}", "").replaceAll("\\{count}", String.valueOf(cheatedStack.getCount())); - Minecraft.getInstance().player.displayClientMessage(Component.translatable("text.rei.too_long_nbt"), false); + Minecraft.getInstance().player.sendSystemMessage(Component.translatable("text.rei.too_long_nbt")); } Minecraft.getInstance().player.connection.sendCommand(StringUtils.removeStart(madeUpCommand, "/")); return true; diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/access/ClientRecipeBookEntriesAccessor.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/access/ClientRecipeBookEntriesAccessor.java new file mode 100644 index 000000000..4407c5466 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/access/ClientRecipeBookEntriesAccessor.java @@ -0,0 +1,33 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.access; + +import net.minecraft.world.item.crafting.display.RecipeDisplayEntry; +import net.minecraft.world.item.crafting.display.RecipeDisplayId; + +import java.util.Map; + +public interface ClientRecipeBookEntriesAccessor { + Map rei$getKnownEntries(); +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java index 19f477f20..47342908e 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java @@ -29,7 +29,6 @@ import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import me.shedaniel.autoconfig.AutoConfig; -import me.shedaniel.autoconfig.gui.ConfigScreenProvider; import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer; import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Jankson; import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.JsonNull; @@ -287,21 +286,6 @@ public void toggleCraftableOnly() { @SuppressWarnings("deprecation") @Override public Screen getConfigScreen(Screen parent) { - if (true) return new REIConfigScreen(parent); - - try { - ConfigScreenProvider provider = (ConfigScreenProvider) AutoConfig.getConfigScreen(ConfigObjectImpl.class, parent); - provider.setBuildFunction(builder -> { - ConfigAddonRegistryImpl addonRegistry = (ConfigAddonRegistryImpl) ConfigAddonRegistry.getInstance(); - if (!addonRegistry.getAddons().isEmpty()) { - builder.getOrCreateCategory(Component.translatable("config.roughlyenoughitems.basics")).getEntries().add(0, new ConfigAddonsEntry(220)); - } - return null; - }); - return provider.get(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; + return new REIConfigScreen(parent); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonsScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonsScreen.java index 8016a3e48..88f5857fa 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonsScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/addon/ConfigAddonsScreen.java @@ -30,6 +30,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ComponentPath; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarratableEntry; @@ -45,7 +46,7 @@ import java.util.List; import java.util.function.Supplier; -public class ConfigAddonsScreen extends Screen { +public class ConfigAddonsScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { private AddonsList rulesList; private final Screen parent; @@ -128,7 +129,7 @@ public DefaultAddonEntry(Screen parent, ConfigAddon addon) { Minecraft.getInstance().setScreen(this.addon.createScreen(Minecraft.getInstance().screen)); }, Supplier::get) { @Override - public void renderContents(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + public void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { graphics.blit(RenderPipelines.GUI_TEXTURED, InternalTextures.CHEST_GUI_TEXTURE, getX() + 3, getY() + 3, 0, 0, 14, 14, 256, 256); } }; @@ -159,7 +160,7 @@ public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth } configureButton.setX(x + entryWidth - 25); configureButton.setY(y + 1); - configureButton.render(graphics, mouseX, mouseY, delta); + configureButton.extractRenderState(graphics, mouseX, mouseY, delta); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ConfigAddonsEntry.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ConfigAddonsEntry.java index d6415fed2..90acff5cc 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ConfigAddonsEntry.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ConfigAddonsEntry.java @@ -30,6 +30,7 @@ import me.shedaniel.rei.impl.client.config.addon.ConfigAddonsScreen; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.events.GuiEventListener; @@ -72,16 +73,19 @@ public Optional getDefaultValue() { public void save() { } - @Override public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) { - super.render(graphics, index, y, x, entryWidth, entryHeight, mouseX, mouseY, isSelected, delta); Window window = Minecraft.getInstance().getWindow(); this.buttonWidget.active = REIRuntime.getInstance().getPreviousContainerScreen() != null && Minecraft.getInstance().getConnection() != null && Minecraft.getInstance().getConnection().registryAccess() != null && this.isEditable(); this.buttonWidget.setY(y); this.buttonWidget.setX(x + entryWidth / 2 - width / 2); this.buttonWidget.setWidth(width); - this.buttonWidget.render(graphics, mouseX, mouseY, delta); + this.buttonWidget.extractRenderState(graphics, mouseX, mouseY, delta); + } + + @Override + public void extractRenderState(GuiGraphicsExtractor graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) { + render(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), index, y, x, entryWidth, entryHeight, mouseX, mouseY, isSelected, delta); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ConfigureCategoriesScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ConfigureCategoriesScreen.java index 0c749234a..f411ccd47 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ConfigureCategoriesScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ConfigureCategoriesScreen.java @@ -47,7 +47,7 @@ import java.util.*; import java.util.function.Supplier; -public class ConfigureCategoriesScreen extends Screen { +public class ConfigureCategoriesScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { private final Map, Boolean> filteringQuickCraftCategories; private final Set> hiddenCategories; private final List> categoryOrdering; @@ -291,10 +291,10 @@ public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth } upButton.setX(x + entryWidth - 20); upButton.setY(y + entryHeight / 2 - 21); - upButton.render(graphics, mouseX, mouseY, delta); + upButton.extractRenderState(graphics, mouseX, mouseY, delta); downButton.setX(x + entryWidth - 20); downButton.setY(y + entryHeight / 2 + 1); - downButton.render(graphics, mouseX, mouseY, delta); + downButton.extractRenderState(graphics, mouseX, mouseY, delta); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringAddRuleScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringAddRuleScreen.java index 77c7f5cba..d1431a783 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringAddRuleScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringAddRuleScreen.java @@ -43,7 +43,7 @@ import java.util.function.Function; import java.util.function.Supplier; -public class FilteringAddRuleScreen extends Screen { +public class FilteringAddRuleScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { private final List> rules; private RulesList rulesList; Screen parent; @@ -167,7 +167,7 @@ public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth } addButton.setX(x + entryWidth - 25); addButton.setY(y + 1); - addButton.render(graphics, mouseX, mouseY, delta); + addButton.extractRenderState(graphics, mouseX, mouseY, delta); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringRulesScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringRulesScreen.java index c45a7c1f4..792fa5dae 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringRulesScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringRulesScreen.java @@ -41,6 +41,7 @@ import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarratableEntry; @@ -59,7 +60,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -public class FilteringRulesScreen extends Screen { +public class FilteringRulesScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { private final FilteringScreen filteringScreen; private final List> rules; private RulesList rulesList; @@ -186,7 +187,7 @@ public DefaultRuleEntry(FilteringRule rule, List> rules, Fun Minecraft.getInstance().setScreen(this.screenFunction.apply(Minecraft.getInstance().screen)); }, Supplier::get) { @Override - public void renderContents(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + public void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { graphics.blit(RenderPipelines.GUI_TEXTURED, InternalTextures.CHEST_GUI_TEXTURE, getX() + 3, getY() + 3, 0, 0, 14, 14, 256, 256); } }; @@ -227,10 +228,10 @@ public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth } configureButton.setX(x + entryWidth - 25); configureButton.setY(y + 1); - configureButton.render(graphics, mouseX, mouseY, delta); + configureButton.extractRenderState(graphics, mouseX, mouseY, delta); deleteButton.setX(x + entryWidth - 27 - deleteButton.getWidth()); deleteButton.setY(y + 1); - deleteButton.render(graphics, mouseX, mouseY, delta); + deleteButton.extractRenderState(graphics, mouseX, mouseY, delta); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringScreen.java index c2fb5d6b5..d03d8fee6 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringScreen.java @@ -67,7 +67,7 @@ import static me.shedaniel.rei.impl.client.gui.widget.entrylist.EntryListWidget.entrySize; @ApiStatus.Internal -public class FilteringScreen extends Screen { +public class FilteringScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { protected List> selected = Lists.newArrayList(); protected final ScrollingContainer scrolling = new ScrollingContainer() { @Override @@ -237,11 +237,11 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { manager.render(graphics, mouseX, mouseY, delta); updatePosition(delta); scrolling.renderScrollBar(graphics, 0, REIRuntime.getInstance().isDarkThemeEnabled() ? 0.8F : 1F); - this.searchField.render(graphics, mouseX, mouseY, delta); - this.selectAllButton.render(graphics, mouseX, mouseY, delta); - this.selectNoneButton.render(graphics, mouseX, mouseY, delta); - this.hideButton.render(graphics, mouseX, mouseY, delta); - this.showButton.render(graphics, mouseX, mouseY, delta); + this.searchField.extractRenderState(graphics, mouseX, mouseY, delta); + this.selectAllButton.extractRenderState(graphics, mouseX, mouseY, delta); + this.selectNoneButton.extractRenderState(graphics, mouseX, mouseY, delta); + this.hideButton.extractRenderState(graphics, mouseX, mouseY, delta); + this.showButton.extractRenderState(graphics, mouseX, mouseY, delta); graphics.disableScissor(); // TODO: add back border @@ -254,7 +254,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { buffer.addVertex(matrix, 0, bounds.y, 0.0F).setColor(0, 0, 0, 255); });*/ - this.backButton.render(graphics, mouseX, mouseY, delta); + this.backButton.extractRenderState(graphics, mouseX, mouseY, delta); if (tooltip != null) { ((ScreenOverlayImpl) REIRuntime.getInstance().getOverlay().get()).renderTooltip(graphics, tooltip); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/REIConfigScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/REIConfigScreen.java index fb7d38972..d52c7eeb8 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/REIConfigScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/REIConfigScreen.java @@ -72,7 +72,7 @@ import static me.shedaniel.rei.impl.client.gui.config.options.ConfigUtils.literal; import static me.shedaniel.rei.impl.client.gui.config.options.ConfigUtils.translatable; -public class REIConfigScreen extends Screen implements ConfigAccess { +public class REIConfigScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen implements ConfigAccess { private final Screen parent; private final List categories; private final List widgets = new ArrayList<>(); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/configure/PanelBoundariesConfiguration.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/configure/PanelBoundariesConfiguration.java index bb71f4ab2..cc11c2b65 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/configure/PanelBoundariesConfiguration.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/configure/PanelBoundariesConfiguration.java @@ -55,7 +55,7 @@ public void configure(ConfigAccess access, CompositeOption option Minecraft.getInstance().setScreen(new BoundariesScreen(access, option, onClose)); } - private static class BoundariesScreen extends Screen { + private static class BoundariesScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { private final ConfigAccess access; private final CompositeOption option; private final Runnable onClose; diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/credits/CreditsScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/credits/CreditsScreen.java index b1b67c587..6d91a382e 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/credits/CreditsScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/credits/CreditsScreen.java @@ -47,7 +47,7 @@ import java.util.stream.Collectors; @ApiStatus.Internal -public class CreditsScreen extends Screen { +public class CreditsScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { private Screen parent; private AbstractButton buttonDone; private CreditsEntryListWidget entryListWidget; diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/error/ErrorsEntryListWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/error/ErrorsEntryListWidget.java index f285fca9a..731d729a7 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/error/ErrorsEntryListWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/error/ErrorsEntryListWidget.java @@ -30,6 +30,7 @@ import net.minecraft.ChatFormatting; import net.minecraft.client.*; import net.minecraft.client.gui.*; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.util.*; import net.minecraft.client.gui.components.events.ContainerEventHandler; import net.minecraft.client.gui.components.events.GuiEventListener; @@ -76,6 +77,10 @@ private Entry _getEntry(int index) { public void _addEntry(Entry entry) { addItem(entry); } + + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + extractRenderState(graphics, mouseX, mouseY, delta); + } @Override public int getItemWidth() { @@ -88,6 +93,14 @@ protected int getScrollbarPosition() { } public static abstract class Entry extends DynamicEntryListWidget.Entry { + public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) { + } + + @Override + public void extractRenderState(GuiGraphicsExtractor graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) { + render(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), index, y, x, entryWidth, entryHeight, mouseX, mouseY, isSelected, delta); + } + @Override public List narratables() { return Collections.emptyList(); @@ -423,7 +436,7 @@ public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth width = (entryWidth - 6) / 2; this.height = (int) ((double) width * ((double) image.getHeight() / (double) image.getWidth())); graphics.fill(x, y, x + width, y + height + 2, 0xFFFFFFFF); - graphics.innerBlit(RenderPipelines.GUI_TEXTURED, id, x + 1, x + width - 1, y + 1, y + height + 1, 0, 0, 1, 0, 1); + graphics.blit(RenderPipelines.GUI_TEXTURED, id, x + 1, y + 1, 0, 0, width - 2, height, width - 2, height); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/error/ErrorsScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/error/ErrorsScreen.java index 4a51a0037..1a368a4a3 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/error/ErrorsScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/error/ErrorsScreen.java @@ -39,7 +39,7 @@ import java.util.function.Supplier; @ApiStatus.Internal -public class ErrorsScreen extends Screen { +public class ErrorsScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { private List components; private AbstractButton doneButton; private ErrorsEntryListWidget listWidget; diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/hints/ImportantWarningsWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/hints/ImportantWarningsWidget.java index e4076e492..1ebac2c03 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/hints/ImportantWarningsWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/hints/ImportantWarningsWidget.java @@ -57,7 +57,8 @@ public ImportantWarningsWidget() { if (((EntryRegistryImpl) EntryRegistry.getInstance()).listeners.add(LISTENER)) { String newId = Minecraft.getInstance().hasSingleplayerServer() ? "integrated:" + Minecraft.getInstance().getSingleplayerServer().getWorldData().getLevelName() - : InstanceHelper.connectionFromClient() != null ? "server:" + InstanceHelper.connectionFromClient().getId() + : InstanceHelper.connectionFromClient() != null && InstanceHelper.connectionFromClient().getServerData() != null + ? "server:" + InstanceHelper.connectionFromClient().getServerData().ip : "null"; if (!newId.equals(prevId)) { prevId = newId; diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/modules/entries/SubMenuEntry.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/modules/entries/SubMenuEntry.java index cc73b75fb..f3a6f5066 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/modules/entries/SubMenuEntry.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/modules/entries/SubMenuEntry.java @@ -118,10 +118,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { menu.bounds.setAs(new FloatingRectangle(facingRight ? createBounds.x : createBounds.getMaxX(), facingDownwards ? createBounds.y : createBounds.getMaxY(), 0.1, 0.1)); } - GuiGraphics.ScissorStack tmp = graphics.scissorStack; - graphics.scissorStack = new GuiGraphics.ScissorStack(); - menu.render(graphics, mouseX, mouseY, delta); - graphics.scissorStack = tmp; + graphics.withFreshScissorStack(() -> menu.render(graphics, mouseX, mouseY, delta)); } } else { this.childMenu = null; diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java index 08d759732..e667f771d 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java @@ -61,6 +61,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; @@ -82,7 +83,7 @@ import java.util.function.UnaryOperator; import java.util.stream.Stream; -public abstract class AbstractDisplayViewingScreen extends Screen implements DisplayScreen { +public abstract class AbstractDisplayViewingScreen extends REIScreen implements DisplayScreen { protected final Map, List> categoryMap; protected final List> categories; protected final TabContainerWidget tabs = new TabContainerWidget(); @@ -401,7 +402,8 @@ public int getWidth(Font font) { } @Override - public void renderImage(Font font, int x, int y, int width, int height, GuiGraphics graphics) { + public void extractImage(Font font, int x, int y, int width, int height, GuiGraphicsExtractor graphics) { + GuiGraphics guiGraphics = graphics instanceof GuiGraphics existing ? existing : new GuiGraphics(graphics); int entrySize = EntryListWidget.entrySize(); int w = Math.max(1, MAX_WIDTH / entrySize); int i = 0; @@ -411,25 +413,26 @@ public void renderImage(Font font, int x, int y, int width, int height, GuiGraph i++; if (i / w > 5) { Component text = Component.literal("+" + (widget.getEntries().size() - w * 6 + 1)).withStyle(ChatFormatting.GRAY); - graphics.pose().pushMatrix(); - graphics.pose().translate(x1 + entrySize / 2 - font.width(text) / 2, y1 + entrySize / 2 - 1); - graphics.drawString(font, text, 0, 0, -1, true); - graphics.pose().popMatrix(); + guiGraphics.pose().pushMatrix(); + guiGraphics.pose().translate(x1 + entrySize / 2 - font.width(text) / 2, y1 + entrySize / 2 - 1); + guiGraphics.drawString(font, text, 0, 0, -1, true); + guiGraphics.pose().popMatrix(); break; } else { - entry.render(graphics, new Rectangle(x1, y1, entrySize, entrySize), -1000, -1000, 0); + entry.render(guiGraphics, new Rectangle(x1, y1, entrySize, entrySize), -1000, -1000, 0); } } } @Override - public void renderText(GuiGraphics graphics, Font font, int x, int y) { - graphics.drawString(font, Component.translatable("text.rei.accepts").withStyle(ChatFormatting.GRAY), x, y + 2, -1); + public void extractText(GuiGraphicsExtractor graphics, Font font, int x, int y) { + GuiGraphics guiGraphics = graphics instanceof GuiGraphics existing ? existing : new GuiGraphics(graphics); + guiGraphics.drawString(font, Component.translatable("text.rei.accepts").withStyle(ChatFormatting.GRAY), x, y + 2, -1); if (widget.tagMatch != null) { int entrySize = EntryListWidget.entrySize(); int w = Math.max(1, MAX_WIDTH / entrySize); - graphics.drawString(font, Component.translatable("text.rei.tag_accept", widget.tagMatch.toString()) + guiGraphics.drawString(font, Component.translatable("text.rei.tag_accept", widget.tagMatch.toString()) .withStyle(ChatFormatting.GRAY), x, y + 16 + Math.min(6, Mth.ceil(widget.getEntries().size() / (float) w)) * entrySize, -1); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ConfigReloadingScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ConfigReloadingScreen.java index 06ab8ca88..82ca6c129 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ConfigReloadingScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ConfigReloadingScreen.java @@ -37,7 +37,7 @@ import java.util.function.Supplier; @ApiStatus.Internal -public class ConfigReloadingScreen extends Screen { +public class ConfigReloadingScreen extends REIScreen { private final Component title; private final BooleanSupplier predicate; private Supplier<@Nullable Component> subtitle = () -> null; diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/MapCloningRecipeFiller.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/REIScreen.java similarity index 51% rename from default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/MapCloningRecipeFiller.java rename to runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/REIScreen.java index 029b77b0d..e1fac7eb3 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/crafting/filler/MapCloningRecipeFiller.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/REIScreen.java @@ -21,35 +21,32 @@ * SOFTWARE. */ -package me.shedaniel.rei.plugin.client.categories.crafting.filler; +package me.shedaniel.rei.impl.client.gui.screen; -import me.shedaniel.rei.api.common.display.Display; -import me.shedaniel.rei.api.common.util.EntryIngredients; -import me.shedaniel.rei.plugin.common.displays.crafting.DefaultCustomShapelessDisplay; -import net.minecraft.world.item.Items; -import net.minecraft.world.item.crafting.MapCloningRecipe; -import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; +public abstract class REIScreen extends Screen { + protected REIScreen(Component title) { + super(title); + } -public class MapCloningRecipeFiller implements CraftingRecipeFiller { - @Override - public Collection apply(RecipeHolder recipe) { - List displays = new ArrayList<>(); - - displays.add(new DefaultCustomShapelessDisplay( - List.of(EntryIngredients.of(Items.FILLED_MAP), EntryIngredients.of(Items.MAP)), - List.of(EntryIngredients.of(Items.FILLED_MAP, 2)), - Optional.of(recipe.id().identifier()))); - - return displays; + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + } + + public void renderBackground(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + extractBackground(graphics, mouseX, mouseY, delta); } - + + public void renderTransparentBackground(GuiGraphics graphics) { + extractTransparentBackground(graphics); + } + @Override - public Class getRecipeClass() { - return MapCloningRecipe.class; + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { + super.extractRenderState(graphics, mouseX, mouseY, delta); + render(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), mouseX, mouseY, delta); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ScreenWithMenu.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ScreenWithMenu.java index db5502918..6514211c4 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ScreenWithMenu.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ScreenWithMenu.java @@ -30,7 +30,7 @@ import net.minecraft.network.chat.Component; import org.jetbrains.annotations.Nullable; -public class ScreenWithMenu extends Screen { +public class ScreenWithMenu extends REIScreen { @Nullable private Menu menu; diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/UncertainDisplayViewingScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/UncertainDisplayViewingScreen.java index c76b0efcf..427ce8d4e 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/UncertainDisplayViewingScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/UncertainDisplayViewingScreen.java @@ -54,7 +54,7 @@ import java.util.List; @ApiStatus.Internal -public class UncertainDisplayViewingScreen extends Screen { +public class UncertainDisplayViewingScreen extends REIScreen { private static final Identifier DEFAULT = Identifier.fromNamespaceAndPath("roughlyenoughitems", "textures/gui/screenshot_default.png"); private static final Identifier COMPOSITE = Identifier.fromNamespaceAndPath("roughlyenoughitems", "textures/gui/screenshot_composite.png"); private final List widgets; diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/WarningAndErrorScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/WarningAndErrorScreen.java index 4472af7bf..6767f5c9d 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/WarningAndErrorScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/WarningAndErrorScreen.java @@ -53,7 +53,7 @@ import java.util.function.Supplier; @ApiStatus.Internal -public class WarningAndErrorScreen extends Screen { +public class WarningAndErrorScreen extends REIScreen { private AbstractWidget buttonExit; private StringEntryListWidget listWidget; private String action; @@ -142,7 +142,7 @@ public void render(GuiGraphics graphics, int int_1, int int_2, float float_1) { } else { graphics.drawCenteredString(this.font, "Errors during Roughly Enough Items' " + action, this.width / 2, 16, 0xFFFFFFFF); } - this.buttonExit.render(graphics, int_1, int_2, float_1); + this.buttonExit.extractRenderState(graphics, int_1, int_2, float_1); } private static class StringEntryListWidget extends DynamicErrorFreeEntryListWidget { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/CollapsibleEntriesScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/CollapsibleEntriesScreen.java index cc8993f67..097481f58 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/CollapsibleEntriesScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/CollapsibleEntriesScreen.java @@ -58,7 +58,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; -public class CollapsibleEntriesScreen extends Screen { +public class CollapsibleEntriesScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { private final Runnable onClose; private final CollapsibleConfigManager.CollapsibleConfigObject configObject; private final List widgets = new ArrayList<>(); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/CollapsibleEntryWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/CollapsibleEntryWidget.java index b35d95e98..138022bc2 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/CollapsibleEntryWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/CollapsibleEntryWidget.java @@ -43,6 +43,7 @@ import me.shedaniel.rei.impl.client.gui.widget.EntryRendererManager; import me.shedaniel.rei.impl.client.gui.widget.EntryWidget; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.renderer.RenderPipelines; @@ -114,7 +115,7 @@ public CollapsibleEntryWidget(boolean custom, Identifier id, Component component CollapsibleEntriesScreen.setupCustom(this.id, this.component.getString(), new ArrayList<>(stacks), this.configObject, markDirty); }, Supplier::get) { @Override - protected void renderContents(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + protected void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { graphics.blit(RenderPipelines.GUI_TEXTURED, InternalTextures.CHEST_GUI_TEXTURE, getX() + 3, getY() + 3, 0, 0, 14, 14, 256, 256); } }; @@ -196,20 +197,20 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { this.toggleButton.setX(bounds.getMaxX() - 4 - toggleButton.getWidth()); this.toggleButton.setY(bounds.getMaxY() - 4 - toggleButton.getHeight()); - this.toggleButton.render(graphics, mouseX, mouseY, delta); + this.toggleButton.extractRenderState(graphics, mouseX, mouseY, delta); if (this.toggleButton.isMouseOver(mouseX, mouseY)) { ScreenOverlayImpl.getInstance().clearTooltips(); } if (this.custom) { this.deleteButton.setX(toggleButton.getX() - 2 - deleteButton.getWidth()); this.deleteButton.setY(bounds.getMaxY() - 4 - deleteButton.getHeight()); - this.deleteButton.render(graphics, mouseX, mouseY, delta); + this.deleteButton.extractRenderState(graphics, mouseX, mouseY, delta); if (this.deleteButton.isMouseOver(mouseX, mouseY)) { ScreenOverlayImpl.getInstance().clearTooltips(); } this.configureButton.setX(deleteButton.getX() - 2 - configureButton.getWidth()); this.configureButton.setY(bounds.getMaxY() - 4 - configureButton.getHeight()); - this.configureButton.render(graphics, mouseX, mouseY, delta); + this.configureButton.extractRenderState(graphics, mouseX, mouseY, delta); if (this.configureButton.isMouseOver(mouseX, mouseY)) { ScreenOverlayImpl.getInstance().clearTooltips(); } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/selection/CustomCollapsibleEntrySelectionScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/selection/CustomCollapsibleEntrySelectionScreen.java index 5f6b53426..ce82ea15c 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/selection/CustomCollapsibleEntrySelectionScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/collapsible/selection/CustomCollapsibleEntrySelectionScreen.java @@ -67,7 +67,7 @@ import static me.shedaniel.rei.impl.client.gui.widget.entrylist.EntryListWidget.entrySize; @ApiStatus.Internal -public class CustomCollapsibleEntrySelectionScreen extends Screen { +public class CustomCollapsibleEntrySelectionScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { private final List> selectedStacks; protected List> selected = Lists.newArrayList(); protected final ScrollingContainer scrolling = new ScrollingContainer() { @@ -225,16 +225,16 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { } updatePosition(delta); scrolling.renderScrollBar(graphics, 0, REIRuntime.getInstance().isDarkThemeEnabled() ? 0.8F : 1F); - this.searchField.render(graphics, mouseX, mouseY, delta); - this.selectAllButton.render(graphics, mouseX, mouseY, delta); - this.selectNoneButton.render(graphics, mouseX, mouseY, delta); - this.addButton.render(graphics, mouseX, mouseY, delta); - this.removeButton.render(graphics, mouseX, mouseY, delta); + this.searchField.extractRenderState(graphics, mouseX, mouseY, delta); + this.selectAllButton.extractRenderState(graphics, mouseX, mouseY, delta); + this.selectNoneButton.extractRenderState(graphics, mouseX, mouseY, delta); + this.addButton.extractRenderState(graphics, mouseX, mouseY, delta); + this.removeButton.extractRenderState(graphics, mouseX, mouseY, delta); graphics.disableScissor(); graphics.fillGradient(0, bounds.y, width, bounds.y + 4, 0xFF000000, 0x00000000); - this.backButton.render(graphics, mouseX, mouseY, delta); + this.backButton.extractRenderState(graphics, mouseX, mouseY, delta); if (tooltip != null) { ScreenOverlayImpl.getInstance().renderTooltip(graphics, tooltip); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/generic/OptionEntriesScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/generic/OptionEntriesScreen.java index 53e2e5a58..ea3bedb3a 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/generic/OptionEntriesScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/generic/OptionEntriesScreen.java @@ -53,7 +53,7 @@ import java.util.function.Function; import java.util.function.Supplier; -public abstract class OptionEntriesScreen extends Screen { +public abstract class OptionEntriesScreen extends me.shedaniel.rei.impl.client.gui.screen.REIScreen { private ListWidget listWidget; public Screen parent; @@ -195,7 +195,7 @@ public TextFieldListEntry(int width, Consumer widgetConsumer) { public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { widget.setX(x + 2); widget.setY(y + 2); - widget.render(graphics, mouseX, mouseY, delta); + widget.extractRenderState(graphics, mouseX, mouseY, delta); } @Override @@ -232,7 +232,7 @@ public ButtonListEntry(int width, Function textFunct public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { widget.setX(x + 2); widget.setY(y); - widget.render(graphics, mouseX, mouseY, delta); + widget.extractRenderState(graphics, mouseX, mouseY, delta); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/toast/CopyRecipeIdentifierToast.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/toast/CopyRecipeIdentifierToast.java index fbf24658c..afc64ec1e 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/toast/CopyRecipeIdentifierToast.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/toast/CopyRecipeIdentifierToast.java @@ -26,6 +26,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.toasts.Toast; import net.minecraft.client.gui.components.toasts.ToastManager; import net.minecraft.client.renderer.RenderPipelines; @@ -68,7 +69,6 @@ public void update(ToastManager toastManager, long l) { this.wantedVisibility = (double) m < d ? Visibility.SHOW : Visibility.HIDE; } - @Override public void render(GuiGraphics graphics, Font font, long var2) { graphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, 0, 0, 0, 0, 160, 32, 256, 256); if (this.subtitle == null) { @@ -78,6 +78,11 @@ public void render(GuiGraphics graphics, Font font, long var2) { graphics.drawString(font, this.subtitle, 18, 18, -16777216, false); } } + + @Override + public void extractRenderState(GuiGraphicsExtractor graphics, Font font, long time) { + render(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), font, time); + } @Override public Object getToken() { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/toast/ExportRecipeIdentifierToast.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/toast/ExportRecipeIdentifierToast.java index a51c4f62d..747a9ead2 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/toast/ExportRecipeIdentifierToast.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/toast/ExportRecipeIdentifierToast.java @@ -26,6 +26,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.toasts.Toast; import net.minecraft.client.gui.components.toasts.ToastManager; import net.minecraft.client.renderer.RenderPipelines; @@ -68,7 +69,6 @@ public void update(ToastManager toastManager, long l) { this.wantedVisibility = (double) m < d ? Visibility.SHOW : Visibility.HIDE; } - @Override public void render(GuiGraphics graphics, Font font, long var2) { graphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, 0, 0, 0, 0, 160, 32, 256, 256); if (this.subtitle == null) { @@ -78,6 +78,11 @@ public void render(GuiGraphics graphics, Font font, long var2) { graphics.drawString(font, this.subtitle, 18, 18, -16777216, false); } } + + @Override + public void extractRenderState(GuiGraphicsExtractor graphics, Font font, long time) { + render(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), font, time); + } @Override public Object getToken() { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/CraftableFilterButtonWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/CraftableFilterButtonWidget.java index 80308ea96..cce44b19e 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/CraftableFilterButtonWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/CraftableFilterButtonWidget.java @@ -52,12 +52,9 @@ import me.shedaniel.rei.impl.common.InternalLogger; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.renderer.entity.ItemRenderer; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Blocks; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -71,8 +68,6 @@ public class CraftableFilterButtonWidget { public static Widget create(ScreenOverlayImpl overlay) { Rectangle bounds = getCraftableFilterBounds(); MenuAccess access = overlay.menuAccess(); - ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); - ItemStack icon = new ItemStack(Blocks.CRAFTING_TABLE); Button filterButton = Widgets.createButton(bounds, Component.empty()) .focusable(false) .onClick(button -> { @@ -86,13 +81,7 @@ public static Widget create(ScreenOverlayImpl overlay) { }) .containsMousePredicate((button, point) -> button.getBounds().contains(point) && overlay.isNotInExclusionZones(point.x, point.y)) .tooltipLineSupplier(button -> Component.translatable(ConfigManager.getInstance().isCraftableOnlyEnabled() ? "text.rei.showing_craftable" : "text.rei.showing_all")); - Widget overlayWidget = Widgets.createDrawableWidget((graphics, mouseX, mouseY, delta) -> { - graphics.pose().pushMatrix(); - graphics.pose().translate(bounds.x + 2, bounds.y + 2); - graphics.renderItem(icon, 0, 0); - graphics.pose().popMatrix(); - }); - return Widgets.concat(filterButton, overlayWidget); + return filterButton; } private static Collection menuEntries(MenuAccess access) { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DisplayTooltipComponent.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DisplayTooltipComponent.java index 067f1f3ee..e349ab458 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DisplayTooltipComponent.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DisplayTooltipComponent.java @@ -38,6 +38,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.world.inventory.tooltip.TooltipComponent; @@ -88,22 +89,23 @@ public int getWidth(Font font) { } @Override - public void renderImage(Font font, int x, int y, int width, int height, GuiGraphics graphics) { - graphics.pose().pushMatrix(); - graphics.pose().translate(x + 2, y + 2); - graphics.pose().translate(-this.bounds.getX(), -this.bounds.getY()); - widget.render(graphics, -1000, -1000, 0); + public void extractImage(Font font, int x, int y, int width, int height, GuiGraphicsExtractor graphics) { + GuiGraphics guiGraphics = graphics instanceof GuiGraphics existing ? existing : new GuiGraphics(graphics); + guiGraphics.pose().pushMatrix(); + guiGraphics.pose().translate(x + 2, y + 2); + guiGraphics.pose().translate(-this.bounds.getX(), -this.bounds.getY()); + widget.render(guiGraphics, -1000, -1000, 0); AutoCraftingEvaluator.AutoCraftingResult craftingResult = autoCraftingResult.get(); if (craftingResult.hasApplicable && craftingResult.renderer != null) { - graphics.pose().pushMatrix(); - Rectangle transformedBounds = MatrixUtils.transform(MatrixUtils.inverse(graphics.pose()), new Rectangle(x + 2, y + 2, bounds.width, bounds.height)); - Point mouse = MatrixUtils.transform(graphics.pose(), PointHelper.ofMouse()); - craftingResult.renderer.render(graphics, mouse.x, mouse.y, Minecraft.getInstance().getDeltaTracker().getRealtimeDeltaTicks(), + guiGraphics.pose().pushMatrix(); + Rectangle transformedBounds = MatrixUtils.transform(MatrixUtils.inverse(guiGraphics.pose()), new Rectangle(x + 2, y + 2, bounds.width, bounds.height)); + Point mouse = MatrixUtils.transform(guiGraphics.pose(), PointHelper.ofMouse()); + craftingResult.renderer.render(guiGraphics, mouse.x, mouse.y, Minecraft.getInstance().getDeltaTracker().getRealtimeDeltaTicks(), widgets, transformedBounds, display.provideInternalDisplay()); - graphics.pose().popMatrix(); + guiGraphics.pose().popMatrix(); } - graphics.pose().popMatrix(); + guiGraphics.pose().popMatrix(); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DynamicErrorFreeEntryListWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DynamicErrorFreeEntryListWidget.java index 710432dda..f6bf40606 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DynamicErrorFreeEntryListWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DynamicErrorFreeEntryListWidget.java @@ -31,6 +31,7 @@ import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Renderable; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarratableEntry; @@ -248,7 +249,6 @@ protected void renderBackBackground(GuiGraphics graphics) { left, top, right, bottom, (int) getScroll(), 32); } - @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { this.drawBackground(); int scrollbarPosition = this.getScrollbarPosition(); @@ -267,6 +267,11 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { this.renderScrollBar(graphics, maxScroll, scrollbarPosition, int_4); this.renderDecorations(graphics, mouseX, mouseY); } + + @Override + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { + render(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), mouseX, mouseY, delta); + } protected void renderScrollBar(GuiGraphics graphics, int maxScroll, int scrollbarPositionMinX, int scrollbarPositionMaxX) { if (maxScroll > 0) { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/UpdatedListWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/UpdatedListWidget.java index 7746e1048..14290d868 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/UpdatedListWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/UpdatedListWidget.java @@ -26,6 +26,7 @@ import me.shedaniel.clothconfig2.gui.widget.DynamicElementListWidget; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.events.GuiEventListener; import java.util.List; @@ -40,8 +41,20 @@ public UpdatedListWidget(Minecraft client, int width, int height, int top, int b public static void renderAs(Minecraft minecraft, int width, int height, int top, int bottom, GuiGraphics graphics, float delta) { new UpdatedListWidget(minecraft, width, height, top, bottom).render(graphics, -100, -100, delta); } + + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + extractRenderState(graphics, mouseX, mouseY, delta); + } public static abstract class Entry> extends DynamicElementListWidget.ElementEntry { + public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) { + } + + @Override + public void extractRenderState(GuiGraphicsExtractor graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) { + render(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), index, y, x, entryWidth, entryHeight, mouseX, mouseY, isSelected, delta); + } + @Override public List children() { return List.of(); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/VanillaWrappedWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/VanillaWrappedWidget.java index 39c48f327..3be4c2b52 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/VanillaWrappedWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/VanillaWrappedWidget.java @@ -49,7 +49,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { } else { graphics.pose().pushMatrix(); if (element instanceof Renderable widget) - widget.render(graphics, mouseX, mouseY, delta); + widget.extractRenderState(graphics, mouseX, mouseY, delta); graphics.pose().popMatrix(); } } @@ -83,4 +83,4 @@ public boolean isDragging() { public boolean containsMouse(double mouseX, double mouseY) { return element.isMouseOver(mouseX, mouseY); } -} \ No newline at end of file +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/entrylist/CollapsedEntriesTooltip.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/entrylist/CollapsedEntriesTooltip.java index ac1519e16..a434197fd 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/entrylist/CollapsedEntriesTooltip.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/entrylist/CollapsedEntriesTooltip.java @@ -29,6 +29,7 @@ import net.minecraft.ChatFormatting; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; @@ -58,7 +59,8 @@ public int getWidth(Font font) { } @Override - public void renderImage(Font font, int x, int y, int weight, int height, GuiGraphics graphics) { + public void extractImage(Font font, int x, int y, int weight, int height, GuiGraphicsExtractor graphics) { + GuiGraphics guiGraphics = graphics instanceof GuiGraphics existing ? existing : new GuiGraphics(graphics); int entrySize = EntryListWidget.entrySize(); int w = Math.max(1, MAX_WIDTH / entrySize); int i = 0; @@ -68,13 +70,13 @@ public void renderImage(Font font, int x, int y, int weight, int height, GuiGrap i++; if (i / w > 3 - 1) { Component text = Component.literal("+" + (stack.getIngredient().size() - w * 3 + 1)).withStyle(ChatFormatting.GRAY); - graphics.pose().pushMatrix(); - graphics.pose().translate(x1 + entrySize / 2 - font.width(text) / 2, y1 + entrySize / 2 - 1); - graphics.drawString(font, text, 0, 0, -1); - graphics.pose().popMatrix(); + guiGraphics.pose().pushMatrix(); + guiGraphics.pose().translate(x1 + entrySize / 2 - font.width(text) / 2, y1 + entrySize / 2 - 1); + guiGraphics.drawString(font, text, 0, 0, -1); + guiGraphics.pose().popMatrix(); break; } else { - entry.render(graphics, new Rectangle(x1, y1, entrySize, entrySize), -1000, -1000, 0); + entry.render(guiGraphics, new Rectangle(x1, y1, entrySize, entrySize), -1000, -1000, 0); } } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/recipe/ClientLocalRecipeManager.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/recipe/ClientLocalRecipeManager.java new file mode 100644 index 000000000..f2a40db88 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/recipe/ClientLocalRecipeManager.java @@ -0,0 +1,187 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.recipe; + +import dev.architectury.registry.ReloadListenerRegistry; +import me.shedaniel.rei.RoughlyEnoughItemsCoreClient; +import me.shedaniel.rei.impl.common.InternalLogger; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.commands.Commands; +import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.resources.Identifier; +import net.minecraft.server.RegistryLayer; +import net.minecraft.server.ReloadableServerResources; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.permissions.PermissionSet; +import net.minecraft.tags.TagLoader; +import net.minecraft.util.Unit; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.item.crafting.RecipeManager; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; + +public final class ClientLocalRecipeManager { + private static final Object LOCK = new Object(); + private static final Identifier RELOAD_ID = Identifier.fromNamespaceAndPath("roughlyenoughitems", "client_local_recipes"); + private static int reloadGeneration = 0; + private static @Nullable ReloadableServerResources resources; + private static boolean initialized = false; + private static boolean reloadListenerRegistered = false; + + private ClientLocalRecipeManager() { + } + + public static void init() { + synchronized (LOCK) { + if (initialized) { + return; + } + initialized = true; + } + + registerReloadListenerIfPossible(); + } + + public static void scheduleReload() { + registerReloadListenerIfPossible(); + Minecraft client = Minecraft.getInstance(); + reload(client.getResourceManager(), ForkJoinPool.commonPool(), client::execute); + } + + public static void clear() { + synchronized (LOCK) { + reloadGeneration++; + resources = null; + } + } + + public static @Nullable RecipeManager getRecipeManager() { + ReloadableServerResources resources = ClientLocalRecipeManager.resources; + return resources == null ? null : resources.getRecipeManager(); + } + + private static LayeredRegistryAccess createClientRecipeRegistryAccess() { + // ReloadableServerResources expects the normal server registry layers even when we run the recipe reload on + // the client. Using the client registry stack fails later because it has [STATIC, REMOTE] instead of the + // server's RELOADABLE layer, and merging the connection composite access duplicates builtin registries. + // + // Starting from RegistryLayer.createRegistryAccess() gives Mojang the exact layer shape it expects: + // STATIC is already populated with the builtin registries, WORLDGEN/DIMENSIONS/RELOADABLE start empty, and + // ReloadableServerRegistries will populate RELOADABLE during loadResources(...). + return RegistryLayer.createRegistryAccess(); + } + + private static void registerReloadListenerIfPossible() { + synchronized (LOCK) { + if (reloadListenerRegistered) { + return; + } + } + + try { + // Register lazily after the client has started up. Architectury can assert if this happens too early + // during entrypoint initialization, but the cache should still work via manual scheduleReload calls. + ReloadListenerRegistry.register(PackType.CLIENT_RESOURCES, (sharedState, preparationExecutor, barrier, reloadExecutor) -> + barrier.wait(Unit.INSTANCE) + .thenComposeAsync(unit -> reload(Minecraft.getInstance().getResourceManager(), preparationExecutor, reloadExecutor), reloadExecutor), RELOAD_ID); + synchronized (LOCK) { + reloadListenerRegistered = true; + } + } catch (AssertionError error) { + InternalLogger.getInstance().warn("Client local recipe reload listener could not be registered yet; continuing with manual reloads only."); + InternalLogger.getInstance().debug("Client local recipe reload listener registration failed.", error); + } + } + + private static CompletableFuture reload(ResourceManager resourceManager, Executor preparationExecutor, Executor reloadExecutor) { + Minecraft client = Minecraft.getInstance(); + ClientPacketListener connection = client.getConnection(); + if (connection == null) { + clear(); + return CompletableFuture.completedFuture(null); + } + + int generation; + synchronized (LOCK) { + generation = ++reloadGeneration; + } + + // Client recipe reloads still go through the server-side resource loader, so we must hand it the normal + // server registry layer shape instead of the client's composite remote registry access. + LayeredRegistryAccess registryAccess; + try { + LayeredRegistryAccess createdRegistryAccess = createClientRecipeRegistryAccess(); + registryAccess = createdRegistryAccess; + } catch (Throwable throwable) { + synchronized (LOCK) { + if (generation == reloadGeneration && client.getConnection() == connection) { + resources = null; + } + } + InternalLogger.getInstance().error("Failed to create client local recipe registry access!", throwable); + RoughlyEnoughItemsCoreClient.queueClientRecipeFallbackSync(); + return CompletableFuture.completedFuture(null); + } + FeatureFlagSet enabledFeatures = connection.enabledFeatures(); + + return ReloadableServerResources.loadResources(resourceManager, registryAccess, + TagLoader.loadTagsForExistingRegistries(resourceManager, connection.registryAccess()), + enabledFeatures, Commands.CommandSelection.INTEGRATED, PermissionSet.NO_PERMISSIONS, + preparationExecutor, reloadExecutor) + .thenAcceptAsync(resources -> { + boolean accepted = false; + synchronized (LOCK) { + if (generation == reloadGeneration && client.getConnection() == connection) { + ClientLocalRecipeManager.resources = resources; + accepted = true; + } + } + + if (accepted) { + InternalLogger.getInstance().debug("Reloaded %d client local recipes.", resources.getRecipeManager().getRecipes().size()); + RoughlyEnoughItemsCoreClient.queueClientRecipeFallbackSync(); + } + }, reloadExecutor) + .exceptionally(throwable -> { + boolean accepted = false; + synchronized (LOCK) { + if (generation == reloadGeneration && client.getConnection() == connection) { + resources = null; + accepted = true; + } + } + + if (accepted) { + InternalLogger.getInstance().error("Failed to reload client local recipes!", throwable); + RoughlyEnoughItemsCoreClient.queueClientRecipeFallbackSync(); + } + return null; + }); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java index 6bcf1e4b9..4eb36fcf7 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java @@ -37,6 +37,8 @@ import me.shedaniel.rei.api.client.registry.display.visibility.DisplayVisibilityPredicate; import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.util.EntryIngredients; import me.shedaniel.rei.api.common.plugins.PluginManager; import me.shedaniel.rei.api.common.registry.ReloadStage; import me.shedaniel.rei.impl.client.gui.widget.favorites.history.DisplayHistoryManager; @@ -55,6 +57,7 @@ public class DisplayRegistryImpl extends AbstractDisplayRegistry implements DisplayRegistry, DisplayConsumerImpl, DisplayGeneratorsRegistryImpl { public static final Object SYNCED = new Object(); + public static final Object CLIENT_FALLBACK = new Object(); private final Map, List>> displayGenerators = new ConcurrentHashMap<>(); private final List> globalDisplayGenerators = new ArrayList<>(); private final List visibilityPredicates = new ArrayList<>(); @@ -83,6 +86,16 @@ public DisplayRegistryImpl() { public void addJob(Runnable job) { this.jobs.add(job); } + + public boolean hasClientFallbackRecipes() { + WeakHashMap origins = this.holder().origins(); + for (Object origin : origins.values()) { + if (isClientFallbackOrigin(origin)) { + return true; + } + } + return false; + } @Override public void acceptPlugin(REIClientPlugin plugin) { @@ -225,6 +238,72 @@ public void removeSyncedRecipes() { this.holder().remove(display); } } + + public ClientFallbackSyncStats syncClientRecipes(Collection recipeBookEntries, Collection localEntries) { + Stopwatch stopwatch = Stopwatch.createStarted(); + removeClientFallbackRecipes(); + int registeredDisplays = 0; + Map, Integer> categoryCounts = new LinkedHashMap<>(); + if (!fillers().isEmpty()) { + for (RecipeDisplayEntry entry : recipeBookEntries) { + try { + Collection displays = tryFillDisplay(entry.display(), DisplayAdditionReason.RECIPE_MANAGER, DisplayAdditionReason.withId(entry.id())); + if (displays.isEmpty()) { + logMissingClientFallbackDisplay("recipe-book", entry); + continue; + } + for (Display display : displays) { + if (!addClientFallbackDisplay(display, new ClientFallbackOrigin(entry))) { + InternalLogger.getInstance().warn("Rejected recipe-book fallback display %s [%s] in category %s for outputs %s", + display.getClass().getName(), entry.id(), display.getCategoryIdentifier(), describeOutputs(entry)); + continue; + } + registeredDisplays++; + categoryCounts.merge(display.getCategoryIdentifier(), 1, Integer::sum); + } + } catch (Throwable e) { + InternalLogger.getInstance().error("Failed to fill client fallback display for recipe: %s [%s]", entry.display(), entry.id(), e); + } + } + for (RecipeDisplayEntry entry : localEntries) { + try { + Collection displays = tryFillDisplay(entry.display(), DisplayAdditionReason.RECIPE_MANAGER); + if (displays.isEmpty()) { + logMissingClientFallbackDisplay("local", entry); + continue; + } + for (Display display : displays) { + if (!addClientFallbackDisplay(display, new ClientFallbackOrigin(entry))) { + InternalLogger.getInstance().warn("Rejected local fallback display %s [%s] in category %s for outputs %s", + display.getClass().getName(), entry.id(), display.getCategoryIdentifier(), describeOutputs(entry)); + continue; + } + registeredDisplays++; + categoryCounts.merge(display.getCategoryIdentifier(), 1, Integer::sum); + } + } catch (Throwable e) { + InternalLogger.getInstance().error("Failed to fill local fallback display for recipe: %s [%s]", entry.display(), entry.id(), e); + } + } + } + InternalLogger.getInstance().debug("Filled %d displays from client recipe fallback in %s%s", + registeredDisplays, stopwatch.stop(), describeCategoryCounts(categoryCounts)); + return new ClientFallbackSyncStats(recipeBookEntries.size(), localEntries.size(), registeredDisplays, Map.copyOf(categoryCounts)); + } + + public void removeClientFallbackRecipes() { + List toRemove = new LinkedList<>(); + WeakHashMap origins = this.holder().origins(); + for (Map.Entry entry : origins.entrySet()) { + if (isClientFallbackOrigin(entry.getValue())) { + toRemove.add(entry.getKey()); + } + } + + for (Display display : toRemove) { + this.holder().remove(display); + } + } private void removeFailedDisplays() { Multimap, Display> failedDisplays = Multimaps.newListMultimap(new HashMap<>(), ArrayList::new); @@ -262,6 +341,53 @@ public void postStage(ReloadStage stage) { public DisplayCache cache() { return holder().cache; } + + public static boolean isClientFallbackOrigin(@Nullable Object origin) { + return origin == CLIENT_FALLBACK || origin instanceof ClientFallbackOrigin; + } + + public record ClientFallbackOrigin(RecipeDisplayEntry entry) { + } + + public record ClientFallbackSyncStats(int recipeBookEntries, int localEntries, int registeredDisplays, + Map, Integer> categoryCounts) { + } + + private static void logMissingClientFallbackDisplay(String source, RecipeDisplayEntry entry) { + InternalLogger.getInstance().warn("No REI display filler matched %s fallback recipe %s [%s] with outputs %s", + source, entry.display().getClass().getName(), entry.id(), describeOutputs(entry)); + } + + private boolean addClientFallbackDisplay(Display display, ClientFallbackOrigin origin) { + if (!DisplayValidator.validate(display)) { + return false; + } + this.holder().add(display, origin); + return true; + } + + private static String describeCategoryCounts(Map, Integer> categoryCounts) { + if (categoryCounts.isEmpty()) { + return ""; + } + + return categoryCounts.entrySet().stream() + .sorted(Map.Entry.comparingByKey(Comparator.comparing(Object::toString))) + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(java.util.stream.Collectors.joining(", ", " [", "]")); + } + + private static String describeOutputs(RecipeDisplayEntry entry) { + try { + List outputs = new ArrayList<>(); + for (EntryStack stack : EntryIngredients.ofItemStacks(entry.resultItems(EntryIngredients.slotDisplayContext()))) { + outputs.add(stack.getIdentifier() + " (" + stack.asFormattedText().getString() + ")"); + } + return outputs.isEmpty() ? "" : outputs.toString(); + } catch (Throwable throwable) { + return ""; + } + } public static class ClientDisplaysHolder extends DisplaysHolderImpl.ByKey { private final DisplayCache cache = new DisplayCacheImpl(false); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/screen/DefaultScreenOverlayRenderer.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/screen/DefaultScreenOverlayRenderer.java index ebed32e3e..d15059c31 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/screen/DefaultScreenOverlayRenderer.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/screen/DefaultScreenOverlayRenderer.java @@ -28,6 +28,7 @@ import me.shedaniel.rei.api.client.gui.screen.DisplayScreen; import me.shedaniel.rei.api.client.registry.screen.OverlayRendererProvider; import me.shedaniel.rei.impl.common.InternalLogger; +import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import org.jetbrains.annotations.Nullable; @@ -86,7 +87,7 @@ public void onApplied(Sink sink) { rendered[0] = 1; resetFocused(screen); if (!(screen instanceof DisplayScreen)) { - sink.render(graphics, mouseX, mouseY, delta); + sink.render(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), mouseX, mouseY, delta); } resetFocused(screen); }; @@ -104,11 +105,11 @@ public void onApplied(Sink sink) { } resetFocused(screen); if (rendered[0] == 0 && !(screen instanceof DisplayScreen) && (!(screen instanceof AbstractContainerScreen) || rendered[0] < 2)) { - sink.render(graphics, mouseX, mouseY, delta); + sink.render(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), mouseX, mouseY, delta); } rendered[0] = 1; if (rendered[0] == 1) { - sink.lateRender(graphics, mouseX, mouseY, delta); + sink.lateRender(graphics instanceof GuiGraphics guiGraphics ? guiGraphics : new GuiGraphics(graphics), mouseX, mouseY, delta); } resetFocused(screen); }; diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/transfer/MissingStacksTooltip.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/transfer/MissingStacksTooltip.java index 238127e81..984042cf9 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/transfer/MissingStacksTooltip.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/transfer/MissingStacksTooltip.java @@ -31,6 +31,7 @@ import net.minecraft.ChatFormatting; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; @@ -66,7 +67,8 @@ public int getWidth(Font font) { } @Override - public void renderImage(Font font, int x, int y, int width, int height, GuiGraphics graphics) { + public void extractImage(Font font, int x, int y, int width, int height, GuiGraphicsExtractor graphics) { + GuiGraphics guiGraphics = graphics instanceof GuiGraphics existing ? existing : new GuiGraphics(graphics); int entrySize = EntryListWidget.entrySize(); int w = Math.max(1, MAX_WIDTH / entrySize); int i = 0; @@ -76,20 +78,21 @@ public void renderImage(Font font, int x, int y, int width, int height, GuiGraph i++; if (i / w > 5) { Component text = Component.literal("+" + (stacks.size() - w * 6 + 1)).withStyle(ChatFormatting.GRAY); - graphics.drawString(font, text, x1 + entrySize / 2 - font.width(text) / 2, y1 + entrySize / 2 - 1, -1); + guiGraphics.drawString(font, text, x1 + entrySize / 2 - font.width(text) / 2, y1 + entrySize / 2 - 1, -1); break; } else { EntryStack stack; if (entry.isEmpty()) stack = EntryStack.empty(); else if (entry.size() == 1) stack = entry.get(0); else stack = entry.get(Mth.floor((System.currentTimeMillis() / 1000 % (double) entry.size()))); - stack.render(graphics, new Rectangle(x1, y1, entrySize, entrySize), -1000, -1000, 0); + stack.render(guiGraphics, new Rectangle(x1, y1, entrySize, entrySize), -1000, -1000, 0); } } } @Override - public void renderText(GuiGraphics graphics, Font font, int x, int y) { - graphics.drawString(font, Component.translatable("text.rei.missing").withStyle(ChatFormatting.GRAY), x, y + 2, -1); + public void extractText(GuiGraphicsExtractor graphics, Font font, int x, int y) { + GuiGraphics guiGraphics = graphics instanceof GuiGraphics existing ? existing : new GuiGraphics(graphics); + guiGraphics.drawString(font, Component.translatable("text.rei.missing").withStyle(ChatFormatting.GRAY), x, y + 2, -1); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java index be1495d38..678d11e0e 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java @@ -30,6 +30,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import me.shedaniel.rei.RoughlyEnoughItemsCoreClient; import me.shedaniel.rei.api.client.config.ConfigObject; import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; import me.shedaniel.rei.api.client.registry.display.DisplayCategory; @@ -51,23 +52,31 @@ import me.shedaniel.rei.impl.client.registry.display.DisplayCache; import me.shedaniel.rei.impl.client.registry.display.DisplayRegistryImpl; import me.shedaniel.rei.impl.client.util.CrashReportUtils; +import me.shedaniel.rei.impl.Internals; import me.shedaniel.rei.impl.common.InternalLogger; import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; import me.shedaniel.rei.impl.display.DisplaySpec; import net.minecraft.CrashReport; import net.minecraft.ReportedException; import net.minecraft.resources.Identifier; +import net.minecraft.util.context.ContextMap; +import net.minecraft.world.item.crafting.display.RecipeDisplayEntry; +import net.minecraft.world.item.crafting.display.SlotDisplayContext; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; @ApiStatus.Internal public class ViewsImpl implements Views { private static final ThreadLocal BUILDER = new ThreadLocal<>(); + private static final Map MISSING_RECIPE_LOGS = new ConcurrentHashMap<>(); + private static final long MISSING_RECIPE_LOG_COOLDOWN_MS = 2000L; @Nullable @Override @@ -105,6 +114,9 @@ private static Map, List> _buildMapFor(ViewSearc List> recipesForStacksWildcard = CollectionUtils.flatMap(recipesForStacks, wildcardFunction); List> usagesForStacksWildcard = CollectionUtils.flatMap(usagesForStacks, wildcardFunction); DisplayRegistry displayRegistry = DisplayRegistry.getInstance(); + if (!recipesForStacks.isEmpty() && !RoughlyEnoughItemsCoreClient.hasReceivedServerDisplaySync()) { + RoughlyEnoughItemsCoreClient.ensureClientRecipeFallbackSyncedForSearch(); + } DisplayCache displayCache = ((DisplayRegistryImpl) displayRegistry).cache(); Map, Set> result = Maps.newHashMap(); @@ -123,7 +135,7 @@ private static Map, List> _buildMapFor(ViewSearc for (Display display : displays) { if (processingVisibilityHandlers && !displayRegistry.isDisplayVisible(configuration.getCategory(), display)) continue; if (!recipesForStacks.isEmpty()) { - if (isRecipesFor(displayCache, recipesForStacks, display)) { + if (isRecipesFor(displayRegistry, displayCache, recipesForStacks, display)) { set.add(display); continue; } @@ -173,7 +185,7 @@ private static Map, List> _buildMapFor(ViewSearc for (Display display : displays) { if (processingVisibilityHandlers && !displayRegistry.isDisplayVisible(configuration.getCategory(), display)) continue; if (!recipesForStacksWildcard.isEmpty()) { - if (isRecipesFor(displayCache, recipesForStacksWildcard, display)) { + if (isRecipesFor(displayRegistry, displayCache, recipesForStacksWildcard, display)) { set.add(display); continue; } @@ -218,6 +230,10 @@ private static Map, List> _buildMapFor(ViewSearc sortingStopwatch.start(); Map, List> sorted = sortDisplays(merged); sortingStopwatch.stop(); + + if (!recipesForStacks.isEmpty() && CollectionUtils.allMatch(sorted.values(), List::isEmpty)) { + logMissingRecipeDiagnostics(builder, displayRegistry, displayCache, recipesForStacks); + } String message = String.format("Built Recipe View in %s for %d categories, %d recipes for, %d usages for and %d live recipe generators.", stopwatch.stop(), categories.size(), recipesForStacks.size(), usagesForStacks.size(), generatorsCount); @@ -268,14 +284,29 @@ public static Set getOrPutEmptyLinkedSet(Map> map, A key) { return map.get(key); } - public static boolean isRecipesFor(@Nullable DisplayCache displayCache, List> stacks, Display display) { + public static boolean isRecipesFor(DisplayRegistry displayRegistry, @Nullable DisplayCache displayCache, List> stacks, Display display) { if (displayCache != null && displayCache.isCached(display)) { for (EntryStack recipesFor : stacks) { - return displayCache.getDisplaysByOutput(recipesFor).contains(display); + if (displayCache.getDisplaysByOutput(recipesFor).contains(display)) { + return true; + } } } - - return checkUsages(stacks, display, display.getOutputEntries()); + + if (checkUsages(stacks, display, display.getOutputEntries())) { + return true; + } + + Object origin = displayRegistry.getDisplayOrigin(display); + if (origin instanceof DisplayRegistryImpl.ClientFallbackOrigin fallbackOrigin) { + return checkFallbackRecipeOutputs(stacks, fallbackOrigin.entry()); + } + + return false; + } + + public static boolean isRecipesFor(@Nullable DisplayCache displayCache, List> stacks, Display display) { + return isRecipesFor(DisplayRegistry.getInstance(), displayCache, stacks, display); } public static boolean isUsagesFor(@Nullable DisplayCache displayCache, List> stacks, Display display) { @@ -301,6 +332,113 @@ private static boolean checkUsages(List> stacks, Display display, return false; } + + private static boolean checkFallbackRecipeOutputs(List> stacks, RecipeDisplayEntry entry) { + try { + ContextMap context = EntryIngredients.slotDisplayContext(); + for (EntryStack result : EntryIngredients.ofItemStacks(entry.resultItems(context))) { + for (EntryStack stack : stacks) { + if (EntryStacks.equalsFuzzy(result, stack)) { + return true; + } + } + } + } catch (Throwable throwable) { + InternalLogger.getInstance().debug("Failed to match fallback recipe output for %s", entry.id(), throwable); + } + + return false; + } + + private static void logMissingRecipeDiagnostics(ViewSearchBuilder builder, DisplayRegistry displayRegistry, @Nullable DisplayCache displayCache, List> stacks) { + String query = stacks.stream() + .map(ViewsImpl::describeStack) + .collect(Collectors.joining(", ")); + long now = System.currentTimeMillis(); + Long previous = MISSING_RECIPE_LOGS.put(query, now); + if (previous != null && now - previous < MISSING_RECIPE_LOG_COOLDOWN_MS) { + return; + } + + boolean processVisibility = builder.isProcessingVisibilityHandlers(); + Set> filteringCategories = builder.getFilteringCategories(); + int totalDisplays = 0; + int visibleDisplays = 0; + int matchedDisplayOutputs = 0; + int matchedFallbackOutputs = 0; + int hiddenMatches = 0; + int cacheMismatches = 0; + int fallbackOutputMismatches = 0; + List examples = new ArrayList<>(); + + for (CategoryRegistry.CategoryConfiguration configuration : CategoryRegistry.getInstance()) { + CategoryIdentifier categoryId = configuration.getCategoryIdentifier(); + if (!filteringCategories.isEmpty() && !filteringCategories.contains(categoryId)) continue; + List displays = displayRegistry.get((CategoryIdentifier) categoryId); + for (Display display : displays) { + totalDisplays++; + boolean visible = !processVisibility || displayRegistry.isDisplayVisible(configuration.getCategory(), display); + if (visible) { + visibleDisplays++; + } + + boolean directMatch = checkUsages(stacks, display, display.getOutputEntries()); + if (directMatch) { + matchedDisplayOutputs++; + if (!visible) { + hiddenMatches++; + } + if (displayCache != null && displayCache.isCached(display) && !isCachedForAnyOutput(displayCache, stacks, display)) { + cacheMismatches++; + } + } + + Object origin = displayRegistry.getDisplayOrigin(display); + if (origin instanceof DisplayRegistryImpl.ClientFallbackOrigin fallbackOrigin) { + boolean fallbackMatch = checkFallbackRecipeOutputs(stacks, fallbackOrigin.entry()); + if (fallbackMatch) { + matchedFallbackOutputs++; + if (!directMatch) { + fallbackOutputMismatches++; + if (examples.size() < 5) { + examples.add("fallback entry " + fallbackOrigin.entry().id() + + " -> display " + display.getClass().getName() + + " in " + display.getCategoryIdentifier() + + " resolved outputs do not match display outputs"); + } + } + } + } + } + } + + InternalLogger.getInstance().warn("Recipe lookup for [%s] produced 0 categories. displays=%d visible=%d matchedOutputs=%d matchedFallbackOutputs=%d hiddenMatches=%d cacheMismatches=%d fallbackOutputMismatches=%d filteringCategories=%s clientFallbackPresent=%s clientFallbackState=%s", + query, totalDisplays, visibleDisplays, matchedDisplayOutputs, matchedFallbackOutputs, hiddenMatches, cacheMismatches, fallbackOutputMismatches, + filteringCategories.isEmpty() ? "" : filteringCategories, + ((DisplayRegistryImpl) displayRegistry).hasClientFallbackRecipes(), + RoughlyEnoughItemsCoreClient.describeClientRecipeFallbackSyncState()); + for (String example : examples) { + InternalLogger.getInstance().warn("Recipe lookup diagnostic: %s", example); + } + if (matchedDisplayOutputs == 0 && matchedFallbackOutputs == 0) { + InternalLogger.getInstance().warn("Recipe lookup diagnostic: no registered display outputs matched [%s].", query); + } else if (hiddenMatches > 0) { + InternalLogger.getInstance().warn("Recipe lookup diagnostic: matched displays exist for [%s], but %d matching displays were hidden by visibility handlers.", query, hiddenMatches); + } + } + + private static boolean isCachedForAnyOutput(DisplayCache displayCache, List> stacks, Display display) { + for (EntryStack stack : stacks) { + if (displayCache.getDisplaysByOutput(stack).contains(display)) { + return true; + } + } + return false; + } + + private static String describeStack(EntryStack stack) { + return stack.getIdentifier() + " (" + stack.asFormattedText().getString() + ")"; + } private static Iterable sortAutoCrafting(Iterable displays) { Set successfulDisplays = new LinkedHashSet<>(); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/networking/DisplaySyncPacket.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/networking/DisplaySyncPacket.java index d808751a6..e495f7e72 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/networking/DisplaySyncPacket.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/networking/DisplaySyncPacket.java @@ -28,6 +28,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import me.shedaniel.rei.RoughlyEnoughItemsNetwork; +import me.shedaniel.rei.RoughlyEnoughItemsCoreClient; import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.impl.client.registry.display.DisplayRegistryImpl; @@ -103,9 +104,11 @@ public Type type() { @Environment(EnvType.CLIENT) public void handle(NetworkManager.PacketContext context) { DisplayRegistryImpl registry = (DisplayRegistryImpl) DisplayRegistry.getInstance(); + RoughlyEnoughItemsCoreClient.markReceivedServerDisplaySync(); if (syncType() == SyncType.SET) { InternalLogger.getInstance().info("[REI Server Display Sync] Received server's request to set %d recipes.", displays().size()); registry.addJob(() -> { + registry.removeClientFallbackRecipes(); registry.removeSyncedRecipes(); for (Display display : displays()) { registry.add(display, DisplayRegistryImpl.SYNCED); @@ -114,6 +117,7 @@ public void handle(NetworkManager.PacketContext context) { } else if (syncType() == SyncType.APPEND) { InternalLogger.getInstance().info("[REI Server Display Sync] Received server's request to append %d recipes.", displays().size()); registry.addJob(() -> { + registry.removeClientFallbackRecipes(); for (Display display : displays()) { registry.add(display, DisplayRegistryImpl.SYNCED); } diff --git a/runtime/src/main/java/me/shedaniel/rei/plugin/client/entry/ItemEntryDefinition.java b/runtime/src/main/java/me/shedaniel/rei/plugin/client/entry/ItemEntryDefinition.java index 5a4ceac88..fac583896 100644 --- a/runtime/src/main/java/me/shedaniel/rei/plugin/client/entry/ItemEntryDefinition.java +++ b/runtime/src/main/java/me/shedaniel/rei/plugin/client/entry/ItemEntryDefinition.java @@ -204,7 +204,7 @@ public Component asFormattedText(EntryStack entry, ItemStack value, T @Override public Stream> getTagsFor(EntryStack entry, ItemStack value) { - Stream> tags = value.getTags(); + Stream> tags = value.typeHolder().tags(); if (value.getItem() instanceof BlockItem blockItem) { tags = Stream.concat(tags, blockItem.getBlock().builtInRegistryHolder().tags()); } diff --git a/runtime/src/main/java/me/shedaniel/rei/plugin/test/REITestCommonPlugin.java b/runtime/src/main/java/me/shedaniel/rei/plugin/test/REITestCommonPlugin.java index d928b969b..c5f6cb36a 100644 --- a/runtime/src/main/java/me/shedaniel/rei/plugin/test/REITestCommonPlugin.java +++ b/runtime/src/main/java/me/shedaniel/rei/plugin/test/REITestCommonPlugin.java @@ -44,8 +44,9 @@ public REITestCommonPlugin() { .executes(context -> { try { Class displayClass = Class.forName("me.shedaniel.rei.plugin.common.displays.DefaultPathingDisplay"); + Item item = context.getArgument("item", ItemInput.class).item().value(); Display display = (Display) displayClass.getDeclaredConstructor(EntryStack.class, EntryStack.class) - .newInstance(EntryStacks.of(context.getArgument("item", ItemInput.class).getItem()), EntryStacks.of(context.getArgument("item", ItemInput.class).getItem())); + .newInstance(EntryStacks.of(item), EntryStacks.of(item)); ServerDisplayRegistry.getInstance().add(display); } catch (Throwable throwable) { throwable.printStackTrace(); diff --git a/runtime/src/main/java/me/shedaniel/rei/plugin/test/REITestPlugin.java b/runtime/src/main/java/me/shedaniel/rei/plugin/test/REITestPlugin.java index cc248df83..b69628b9f 100644 --- a/runtime/src/main/java/me/shedaniel/rei/plugin/test/REITestPlugin.java +++ b/runtime/src/main/java/me/shedaniel/rei/plugin/test/REITestPlugin.java @@ -65,7 +65,7 @@ public REITestPlugin() { .then(Commands.argument("item", ItemArgument.item(registry)) .executes(context -> { BasicFilteringRule basic = FilteringRuleTypeRegistry.getInstance().basic(); - basic.hide(EntryStacks.of(context.getArgument("item", ItemInput.class).createItemStack(1, false))); + basic.hide(EntryStacks.of(context.getArgument("item", ItemInput.class).createItemStack(1))); return 0; })) .executes(context -> { diff --git a/settings.gradle b/settings.gradle index 9d2f80904..bb437be7d 100755 --- a/settings.gradle +++ b/settings.gradle @@ -1,21 +1,14 @@ pluginManagement { repositories { - maven { url "https://maven.shedaniel.me/" } maven { url "https://maven.fabricmc.net/" } + maven { url "https://maven.shedaniel.me/" } + maven { url "https://maven.architectury.dev/" } gradlePluginPortal() - maven { url "https://files.minecraftforge.net/maven/" } } } -if (JavaVersion.current().ordinal() + 1 < 21) { - throw new IllegalStateException("Please run gradle with Java 21+!") +if (JavaVersion.current().ordinal() + 1 < 25) { + throw new IllegalStateException("Please run gradle with Java 25+!") } rootProject.name = "RoughlyEnoughItems" - -include "api" -include "default-plugin" -include "runtime" -include "fabric" -//include "forge" -include "neoforge"