diff --git a/build.gradle b/build.gradle index 70915fc0..cb462d12 100644 --- a/build.gradle +++ b/build.gradle @@ -2,21 +2,7 @@ tableau { project { group = "com.ldtteam" publisher = "LDTTeam" - modId = "domum_ornamentum" - } - - sourceSets { - api { - isPartOfPrimaryJar = true - neogradle { - isModSource = true - } - } - main { - dependencies { - implementation sourceSets.named('api') - } - } + modId = "blockui" } maven { @@ -27,4 +13,53 @@ tableau { usingGit() }) } -} \ No newline at end of file +} + +dependencies { + testImplementation 'junit:junit:4.13.2' +} + +// NeoGradle 7.1.x does not strip @OnlyIn annotations that Vineflower adds back +// during decompilation for MC 26.x. The NeoForm and UserDev patches expect them +// removed before nested type declarations. We also restore blank-line separators. +tasks.matching { it.name == 'neoFormPatch' }.configureEach { + doFirst { + def decompileJar = project.file("build/neoForm/neoFormJoined26.2-2/steps/decompile/outputs.jar") + if (!decompileJar.exists()) return + + def workDir = new File(project.buildDir, "onlyin-fix") + workDir.deleteDir() + workDir.mkdirs() + + def zipTree = project.zipTree(decompileJar) + def javaFiles = [] + zipTree.visit { details -> + def dest = new File(workDir, details.path) + if (details.directory) { + return + } + if (!dest.name.endsWith('.java')) { + return + } + + dest.parentFile.mkdirs() + details.copyTo(dest) + javaFiles << dest + } + + javaFiles.each { f -> + def text = f.text + def fixed = text.replaceAll( + /(?m)^( +)@OnlyIn\s*\([^)]*\)\n(\1(?:public |private |protected )?(?:static |final |abstract )?(?:class |interface |record |enum |@interface ))/, + '$2') + fixed = fixed.replaceAll( + /(?m)^(\}+)\n( +(?:public|private|protected)\s+(?:static\s+)?(?:class |record |interface |enum ))/, + '$1\n\n$2') + if (fixed != text) f.text = fixed + } + + def ant = new groovy.ant.AntBuilder() + ant.jar(destfile: decompileJar.absolutePath, basedir: workDir, update: true) + workDir.deleteDir() + } +} diff --git a/gradle.properties b/gradle.properties index 1fd55f0a..afd28bdc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,25 +1,34 @@ +org.gradle.jvmargs=-Xmx3G org.gradle.daemon=true org.gradle.parallel=true org.gradle.caching=true -org.gradle.configuration-cache=true +org.gradle.configuration-cache=false -local.version=0.0.1 +modId=blockui +modGroup=com.ldtteam +local.version=0.0.1-local javaVersion=25 +useJavaToolChains=true -modId=blockui -type=MOD -description=XML Based Minecraft UI Library +# Minecraft and NeoForge +minecraft.version=26.2 +minecraft.additionalVersions= +neoforge.version=26.2.0.66 -#The currently running forge. -neoforge.version=26.1.0.0-alpha.11+snapshot-7 +fml_range=[11,) +neoforge_range=[26.2,) +minecraft_range=[26.2, 27) +#Semicolon seperated list of mc versions, which are marked as compatible on curseforge +additionalMinecraftVersions= -curseId=522992 -usesCurse=true +githubUrl=https://github.com/ldtteam/BlockUI +gitUrl=https://github.com/ldtteam/BlockUI.git +gitConnectUrl=https://github.com/ldtteam/BlockUI.git +projectUrl=https://github.com/ldtteam/BlockUI -### Minecraft -minecraft.version=26.1-snapshot-7 -minecraft.additionalVersions= +curse.id=522992 +usesCurse=true useDefaultTestSystem=true runtimeSourceSets=main diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..d997cfc6 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradlew b/gradlew index faf93008..0262dcbd 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c..e509b2dd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle b/settings.gradle index 22dd5bcc..38ee5983 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,7 +10,7 @@ pluginManagement { } plugins { - id 'com.ldtteam.tableau' version '0.0.87' + id 'com.ldtteam.tableau' version '0.0.96-j21.0' id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' } @@ -19,5 +19,4 @@ rootProject.name = 'blockui' features { usesGit = true usesCurse = true - usesParchment = true -} \ No newline at end of file +} diff --git a/src/main/java/com/ldtteam/blockui/AtlasManager.java b/src/main/java/com/ldtteam/blockui/AtlasManager.java deleted file mode 100644 index 09519f02..00000000 --- a/src/main/java/com/ldtteam/blockui/AtlasManager.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.ldtteam.blockui; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiSpriteManager; -import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; -import net.minecraft.client.renderer.texture.TextureAtlas; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.renderer.texture.TextureManager; -import net.minecraft.client.resources.TextureAtlasHolder; -import net.minecraft.client.resources.metadata.gui.GuiMetadataSection; -import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling; -import net.minecraft.resources.Identifier; -import net.minecraft.server.packs.resources.PreparableReloadListener; -import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -/** - * Splits global vanilla gui atlas on per mod-id basis. Requires atlas definition in atlases/_gui.json. "directory" sources in - * the atlas definition must have unique path (ideally contain mod_id) because they join across all mod ids (blame mojang). - */ -public class AtlasManager -{ - private static final Logger LOGGER = LoggerFactory.getLogger(AtlasManager.class); - public static final AtlasManager INSTANCE = new AtlasManager(); - - private final Map modAtlases = new HashMap<>(); - - private AtlasManager() - {} - - /** - * @param resourceRegistry lambda {@link RegisterClientReloadListenersEvent#registerReloadListener(PreparableReloadListener)} - * @param modId owning mod id - */ - public void addAtlas(final Consumer resourceRegistry, final String modId) - { - modAtlases.computeIfAbsent(modId, id -> { - final CustomGuiSpriteManager spriteManager = new CustomGuiSpriteManager(Minecraft.getInstance().getTextureManager(), id); - resourceRegistry.accept(spriteManager); - return spriteManager; - }); - } - - /** - * @return sprite for given resLoc, checks in order: custom mod atlases > vanilla - */ - public TextureAtlasSprite getSprite(final Identifier resLoc) - { - final CustomGuiSpriteManager spriteManager = modAtlases.get(resLoc.getNamespace()); - if (spriteManager != null) - { - final TextureAtlasSprite sprite = spriteManager.getSprite(resLoc); - if (sprite.contents().name() != MissingTextureAtlasSprite.getLocation()) - { - return sprite; - } - } - return Minecraft.getInstance().getGuiSprites().getSprite(resLoc); - } - - /** - * Dump texture content and coordinates description per each registered modid. - * - * @param dumpingFolder ideally empty target folder, will be created if doesn't exist - * @see TextureAtlas#dumpContents(Identifier, Path) - */ - public void dumpAtlases(final Path dumpingFolder) - { - modAtlases.forEach((modId, spriteManager) -> { - try - { - spriteManager.textureAtlas.dumpContents(spriteManager.textureAtlas.location(), - Files.createDirectories(dumpingFolder.resolve(modId))); - } - catch (final IOException e) - { - LOGGER.warn("Failed to dump atlas for mod id: " + modId, e); - } - }); - } - - /** - * @return sprite scaling from given sprite - */ - public static GuiSpriteScaling getSpriteScaling(final TextureAtlasSprite textureAtlasSprite) - { - return textureAtlasSprite.contents() - .metadata() - .getSection(GuiMetadataSection.TYPE) - .orElse(GuiMetadataSection.DEFAULT) - .scaling(); - } - - /** - * Based on {@link GuiSpriteManager} - */ - private class CustomGuiSpriteManager extends TextureAtlasHolder - { - private CustomGuiSpriteManager(final TextureManager textureManager, final String modId) - { - super(textureManager, - Identifier.fromNamespaceAndPath(modId, "textures/atlas/" + modId + "_gui.png"), - Identifier.fromNamespaceAndPath(modId, modId + "_gui"), - GuiSpriteManager.METADATA_SECTIONS); - } - - @Override - public TextureAtlasSprite getSprite(final Identifier resLoc) - { - return super.getSprite(resLoc); - } - } -} diff --git a/src/main/java/com/ldtteam/blockui/BOGuiGraphics.java b/src/main/java/com/ldtteam/blockui/BOGuiGraphics.java index 856f2e4b..b0052688 100644 --- a/src/main/java/com/ldtteam/blockui/BOGuiGraphics.java +++ b/src/main/java/com/ldtteam/blockui/BOGuiGraphics.java @@ -1,26 +1,31 @@ package com.ldtteam.blockui; -import com.ldtteam.blockui.util.SingleBlockGetter.SingleBlockNeighborhood; -import net.minecraft.client.gui.GuiGraphics; - -public class BOGuiGraphics +import com.ldtteam.blockui.util.cursor.Cursor; +import com.ldtteam.common.fakelevel.SingleBlockFakeLevel; +import com.mojang.blaze3d.platform.cursor.CursorType; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.renderer.state.gui.GuiRenderState; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix3x2fStack; + +public class BOGuiGraphics extends GuiGraphicsExtractor { - // Static instance should be fine since gui rendering is on single thread - private static final SingleBlockNeighborhood NEIGHBORHOOD = new SingleBlockNeighborhood(); + private SingleBlockFakeLevel fakeLevel = null; - private GuiGraphics guiGraphics; + private int cursorMaxDepth = -1; + private CursorType selectedCursor = Cursor.DEFAULT; - public BOGuiGraphics(final GuiGraphics ms) + public BOGuiGraphics(final Minecraft mc, final CountingMatrix3x2fStack ps, final GuiRenderState renderState, final int mx, final int my) { - guiGraphics = ms; + super(mc, ps, renderState, mx, my); } - public GuiGraphics guiGraphics() - { - return guiGraphics; - } - - /*private Font getFont(@Nullable final ItemStack itemStack) + private Font getFont(@Nullable final ItemStack itemStack) { if (itemStack != null) { @@ -35,140 +40,97 @@ public GuiGraphics guiGraphics() public void renderItemDecorations(final ItemStack itemStack, final int x, final int y) { - super.renderItemDecorations(getFont(itemStack), itemStack, x, y); + super.itemDecorations(getFont(itemStack), itemStack, x, y); } public void renderItemDecorations(final ItemStack itemStack, final int x, final int y, @Nullable final String altStackSize) { - super.renderItemDecorations(getFont(itemStack), itemStack, x, y, altStackSize); + super.itemDecorations(getFont(itemStack), itemStack, x, y, altStackSize); } - public int drawString(final String text, final float x, final float y, final int color) + public int drawString(final String text, final int x, final int y, final int color) { return drawString(text, x, y, color, false); } - public int drawString(final String text, final float x, final float y, final int color, final boolean shadow) + public int drawString(final String text, final int x, final int y, final int color, final boolean shadow) { - return super.drawString(minecraft.font, text, x, y, color, shadow); + super.text(minecraft.font, text, x, y, color, shadow); + return x + minecraft.font.width(text); // should return end pos } - public void setCursor(final Cursor cursor) + public void setCursor(final CursorType cursor) { - if (guiGraphics.requestCursor(cu).pose().poseStack.size() >= cursorMaxDepth) + final int size = ((CountingMatrix3x2fStack) pose()).size; + if (size >= cursorMaxDepth) { - cursorMaxDepth = pose().poseStack.size(); + cursorMaxDepth = size; selectedCursor = cursor; } - }*/ + } /** * @param debugXoffset debug string x offset */ - /*public void applyCursor(final int debugXoffset) + public CursorType applyCursor(final int debugXoffset) { - selectedCursor.apply(); - if (Pane.debugging) { drawString(selectedCursor.toString(), debugXoffset, -minecraft.font.lineHeight, Color.getByName("white")); } + + // requestCursor(selectedCursor); + // need to direct this to vanilla gui + return selectedCursor; } - /** - * Render given blockState with model just like {@link #renderItem(ItemStack, int, int)} - * - * @param data blockState rendering data - * @param itemStack backing itemStack for given blockState - */ - /*public void renderBlockStateAsItem(final BlockStateRenderingData data, final ItemStack itemStack) + public static double getAltSpeedFactor(final Minecraft mc) { - BakedModel itemModel = minecraft.getItemRenderer().getModel(itemStack, null, null, 0); - if (!itemModel.isGui3d() || data.blockState().getRenderShape() == RenderShape.INVISIBLE) - { - // well, some items are bit dumb - itemModel = minecraft.getItemRenderer().getModel(new ItemStack(Blocks.STONE), null, null, 0); - } - - // prepare pose just like itemStack rendering would do + return mc.hasAltDown() ? 5 : 1; + } - pose().pushPose(); - pose().last().normal().identity(); // reset normals cuz lighting - pose().translate(8, 8, 150); - pose().scale(16.0F, -16.0F, 16.0F); - ClientHooks.handleCameraTransforms(pose(), itemModel, ItemDisplayContext.GUI, false); + public ScreenRectangle calcTransformedPaneBounds(final Pane pane) + { + return new ScreenRectangle(0, 0, pane.getWidth(), pane.getHeight()).transformAxisAligned(pose()); + } - if (data.modelNeedsRotationFix()) + public SingleBlockFakeLevel getFakeLevel() + { + if (fakeLevel == null) { - final Matrix3f oldNormal = pose().last().normal(); - pose().pushPose(); - pose().rotateAround(Axis.YP.rotationDegrees(45), 0.0f, 0.5f, 0.0f); - pose().last().normal().set(oldNormal.rotate(Axis.YP.rotationDegrees(-45))); + fakeLevel = new SingleBlockFakeLevel(Minecraft.getInstance().level); } + return fakeLevel; + } - pose().translate(-0.5F, -0.5F, -0.5F); - - // render block and BE + public static class CountingMatrix3x2fStack extends Matrix3x2fStack + { + private int size = 0; - final int light = LightTexture.pack(15, 15); - minecraft.getBlockRenderer() - .renderSingleBlock(data.blockState(), pose(), bufferSource(), light, OverlayTexture.NO_OVERLAY, data.modelData(), null); - if (data.blockEntity() != null) + public CountingMatrix3x2fStack(final int stackSize) { - try - { - minecraft.getBlockEntityRenderDispatcher() - .getRenderer(data.blockEntity()) - .render(data.blockEntity(), 0, pose(), bufferSource(), light, OverlayTexture.NO_OVERLAY); - } - catch (final Exception e) - { - // well, noop then - } + super(stackSize); } - flush(); - if (data.modelNeedsRotationFix()) + @Override + public Matrix3x2fStack clear() { - pose().popPose(); - pose().translate(-0.5F, -0.5F, -0.5F); + size = 0; + return super.clear(); } - // render fluid - - final FluidState fluidState = data.blockState().getFluidState(); - if (!fluidState.isEmpty()) + @Override + public Matrix3x2fStack popMatrix() { - final RenderType renderType = ItemBlockRenderTypes.getRenderLayer(fluidState); - pushMvApplyPose(); - - NEIGHBORHOOD.blockState = data.blockState(); - minecraft.getBlockRenderer() - .renderLiquid(BlockPos.ZERO, NEIGHBORHOOD, bufferSource().getBuffer(renderType), data.blockState(), fluidState); - - bufferSource().endBatch(renderType); - popMvPose(); + size--; + return super.popMatrix(); } - pose().popPose(); - } - - public void pushMvApplyPose() - { - RenderSystem.getModelViewStack().pushMatrix(); - RenderSystem.getModelViewStack().mul(pose().last().pose()); - RenderSystem.applyModelViewMatrix(); - } - - public void popMvPose() - { - RenderSystem.getModelViewStack().popMatrix(); - RenderSystem.applyModelViewMatrix(); - } - - public static double getAltSpeedFactor() - { - return Screen.hasAltDown() ? 5 : 1; + @Override + public Matrix3x2fStack pushMatrix() + { + size++; + return super.pushMatrix(); + } } - */ } diff --git a/src/main/java/com/ldtteam/blockui/BOScreen.java b/src/main/java/com/ldtteam/blockui/BOScreen.java index 963481dc..c3643388 100644 --- a/src/main/java/com/ldtteam/blockui/BOScreen.java +++ b/src/main/java/com/ldtteam/blockui/BOScreen.java @@ -1,17 +1,23 @@ package com.ldtteam.blockui; -import com.ldtteam.blockui.util.cursor.CursorUtils; import com.ldtteam.blockui.views.BOWindow; -import net.minecraft.client.input.CharacterEvent; -import net.minecraft.client.input.KeyEvent; -import net.minecraft.client.input.MouseButtonEvent; +import com.mojang.blaze3d.platform.Window; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.ReportedException; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.input.PreeditEvent; import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.Projection; +import net.minecraft.client.renderer.state.WindowRenderState; import net.minecraft.network.chat.Component; +import org.joml.Matrix2f; +import org.joml.Matrix3x2f; +import org.joml.Matrix4f; import org.lwjgl.glfw.GLFW; import java.util.Objects; @@ -45,52 +51,100 @@ public BOScreen(final BOWindow w) } @Override - public void render(final GuiGraphics ms, final int mx, final int my, final float f) + public void extractRenderState(final GuiGraphicsExtractor ms, final int mx, final int my, final float f) { - if (minecraft == null || !isOpen) // should never happen though + if (ms.minecraft == null || !isOpen) // should never happen though { return; } + final WindowRenderState windowState = ms.minecraft.gameRenderer.gameRenderState().windowRenderState; absoluteMouseX = mx; absoluteMouseY = my; - framebufferWidth = ms.minecraft.getWindow().getWidth(); - framebufferHeight = ms.minecraft.getWindow().getHeight(); - final int guiWidth = Math.max(framebufferWidth, 320); - final int guiHeight = Math.max(framebufferHeight, 240); + framebufferWidth = windowState.width; + framebufferHeight = windowState.height; + final int guiWidth = Math.max(framebufferWidth, Window.BASE_WIDTH); + final int guiHeight = Math.max(framebufferHeight, Window.BASE_HEIGHT); - mcScale = ms.minecraft.getWindow().getGuiScale(); + mcScale = windowState.guiScale; renderScale = window.getRenderType().calcRenderScale(ms.minecraft.getWindow(), window); - if (window.hasLightbox() && ms.minecraft.screen == this) - { - ms.fillGradient(0, 0, framebufferWidth, framebufferHeight, -1072689136, -804253680); - } - width = window.getWidth(); height = window.getHeight(); x = Math.floor((guiWidth - width * renderScale) / 2.0d); y = Math.floor((guiHeight - height * renderScale) / 2.0d); + // counter vanilla projection + final Projection vanillaProjection = new Projection(); + // INLINE: this is copied from wherever vanilla is doing projection for GUI + vanillaProjection.setupOrtho(1000.0F, + 11000.0F, + (float) windowState.width / windowState.guiScale, + (float) windowState.height / windowState.guiScale, + true); + + final Matrix4f oldProjection = vanillaProjection.getMatrix(new Matrix4f()); + final Matrix4f oldViewModel = new Matrix4f().setTranslation(0.0F, 0.0F, -11000.0F); + // INLINE: end + + final Matrix4f ourProjection = new Matrix4f().setOrtho(0.0F, framebufferWidth, framebufferHeight, 0.0F, vanillaProjection.zNear(), vanillaProjection.zFar()); + + // if ever vanilla decides allows to use 1.21 system immediately do so + // this hack is based on following facts at the time when it was written: + // 1) the only thing that does matrix computation is vertex shader (VS) + // 2) the said VS does classic vertex math: output = P * VM * input + // 3) there is no other math (or scissoring stuff) evolder around old P * VM + // 4) the old P and VM are properly copied from vanilla (INLINE above) + // 5) the matrixes are stable enough to not under/overflow + final Matrix4f hack = new Matrix4f(); + hack.mul(oldViewModel.invert()); + hack.mul(oldProjection.invertOrtho()); + hack.mul(ourProjection); + + final var newMs = new BOGuiGraphics.CountingMatrix3x2fStack(16); + // inject hack + newMs.mul(new Matrix3x2f(new Matrix2f(hack.m00(), hack.m01(), hack.m10(), hack.m11()))); + // our stuff, this assumes projection matrix is size of FB + try { - final BOGuiGraphics target = new BOGuiGraphics(ms); - window.draw(target, calcRelativeX(mx), calcRelativeY(my)); + final double newMx = calcRelativeX(mx), newMy = calcRelativeY(my); + final BOGuiGraphics target = new BOGuiGraphics(ms.minecraft, newMs, ms.guiRenderState, (int) newMx, (int) newMy); - if (ms.minecraft.screen == this) + if (window.hasBlurredBackground() && ms.minecraft.gui.screen() == this && target.guiRenderState.firstStratumAfterBlur == Integer.MAX_VALUE) { + target.blurBeforeThisStratum(); + } + + if (window.hasLightbox() && ms.minecraft.gui.screen() == this) + { + UiRenderMacros.fillGradient(target, 0, 0, framebufferWidth, framebufferHeight, -1072689136, -804253680); + // super.extractTransparentBackground(target); + } + + newMs.translate((float) x, (float) y); + newMs.scale((float) renderScale, (float) renderScale); + + window.draw(target, newMx, newMy); + + if (ms.minecraft.gui.screen() == this) + { + int debugX = (int) (-x / renderScale) + 3; if (Pane.debugging) { - target.guiGraphics().drawString(minecraft.font, + debugX = target.drawString( "XML: %s Scaling: %s (vanilla: %.2f our: %.2f) " .formatted(window.getXmlResourceLocation(), window.getRenderType().name(), mcScale, renderScale), - (int) (-x / renderScale) + 3, + debugX, -minecraft.font.lineHeight, Color.getByName("white")); } + ms.requestCursor(target.applyCursor(debugX)); } - window.drawLast(target, calcRelativeX(mx), calcRelativeY(my)); + target.nextStratum(); // TODO: simulate Z layering a bit, we really need to write sorted layering for us to be stable.. + + window.drawLast(target, newMx, newMy); } catch (final Exception e) { @@ -104,22 +158,28 @@ public void render(final GuiGraphics ms, final int mx, final int my, final float } } + @Override // INLINE: partial inline - completely remove any extraction + public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + this.minecraft.gui.hud.extractDeferredSubtitles(); + } + @Override public boolean keyPressed(final KeyEvent event) { + final int key = event.key(); // keys without printable representation - if (event.key() >= 0 && event.key() <= GLFW.GLFW_KEY_LAST) + if (key >= 0 && key <= GLFW.GLFW_KEY_LAST) { try { - return window.onKeyTyped(String.valueOf('\0'), event.key()); + return window.onKeyEvent(event); } catch (final Exception e) { final CrashReport crashReport = CrashReport.forThrowable(e, "KeyPressed event for BO screen"); final CrashReportCategory category = crashReport.addCategory("BO screen key event details"); category.setDetail("XML res loc", () -> window.getXmlResourceLocation().toString()); - category.setDetail("GLFW key value", () -> Integer.toString(event.key())); + category.setDetail("GLFW key value", () -> Integer.toString(event.input())); throw new ReportedException(crashReport); } } @@ -131,32 +191,40 @@ public boolean charTyped(final CharacterEvent event) { try { - return window.onKeyTyped(event.codepointAsString(), event.codepoint()); + return window.onCharactedEvent(event); } catch (final Exception e) { final CrashReport crashReport = CrashReport.forThrowable(e, "CharTyped event for BO screen"); final CrashReportCategory category = crashReport.addCategory("BO screen char event details"); category.setDetail("XML res loc", () -> window.getXmlResourceLocation().toString()); - category.setDetail("Char value", () -> event.codepointAsString()); + category.setDetail("Char value", () -> Character.toString(event.codepoint())); throw new ReportedException(crashReport); } } + @Override + public boolean preeditUpdated(final PreeditEvent event) + { + // TODO: implement this in text field + return true; + } + @Override public boolean mouseClicked(final MouseButtonEvent event, final boolean doubleClick) { + final int keyCode = event.button(); final double mx = calcRelativeX(event.x()); final double my = calcRelativeY(event.y()); try { - if (event.isLeft()) + if (keyCode == GLFW.GLFW_MOUSE_BUTTON_LEFT) { // Adjust coordinate to origin of window isMouseLeftDown = true; return window.click(mx, my); } - else if (event.isRight()) + else if (keyCode == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { return window.rightClick(mx, my); } @@ -166,7 +234,7 @@ else if (event.isRight()) final CrashReport crashReport = CrashReport.forThrowable(e, "MousePressed event for BO screen"); final CrashReportCategory category = crashReport.addCategory("BO screen mouse event details"); category.setDetail("XML res loc", () -> Objects.toString(window.getXmlResourceLocation())); - category.setDetail("GLFW mouse key value", () -> Integer.toString(event.input())); + category.setDetail("GLFW mouse key value", () -> Integer.toString(keyCode)); throw new ReportedException(crashReport); } return false; @@ -194,11 +262,11 @@ public boolean mouseScrolled(final double mx, final double my, final double scro } @Override - public boolean mouseDragged(final MouseButtonEvent event, final double dx, final double dy) + public boolean mouseDragged(final MouseButtonEvent event, final double deltaX, final double deltaY) { try { - return window.onMouseDrag(calcRelativeX(event.x()), calcRelativeY(event.y()), dx, dy); + return window.onMouseDrag(calcRelativeX(event.x()), calcRelativeY(event.y()), 0, deltaX, deltaY); } catch (final Exception e) { @@ -212,7 +280,8 @@ public boolean mouseDragged(final MouseButtonEvent event, final double dx, final @Override public boolean mouseReleased(final MouseButtonEvent event) { - if (event.isLeft()) + final int keyCode = event.button(); + if (keyCode == GLFW.GLFW_MOUSE_BUTTON_LEFT) { // Adjust coordinate to origin of window isMouseLeftDown = false; @@ -225,7 +294,7 @@ public boolean mouseReleased(final MouseButtonEvent event) final CrashReport crashReport = CrashReport.forThrowable(e, "MouseReleased event for BO screen"); final CrashReportCategory category = crashReport.addCategory("BO screen mouse event details"); category.setDetail("XML res loc", () -> window.getXmlResourceLocation().toString()); - category.setDetail("GLFW mouse key value", () -> Integer.toString(event.input())); + category.setDetail("GLFW mouse key value", () -> Integer.toString(keyCode)); throw new ReportedException(crashReport); } } @@ -293,7 +362,6 @@ public void removed() finally { BOWindow.clearFocus(); - CursorUtils.resetCursor(); } } diff --git a/src/main/java/com/ldtteam/blockui/Loader.java b/src/main/java/com/ldtteam/blockui/Loader.java index 2fcaeab4..454dddeb 100644 --- a/src/main/java/com/ldtteam/blockui/Loader.java +++ b/src/main/java/com/ldtteam/blockui/Loader.java @@ -1,7 +1,9 @@ package com.ldtteam.blockui; import com.ldtteam.blockui.controls.*; +import com.ldtteam.blockui.mod.BlockUI; import com.ldtteam.blockui.mod.Log; +import com.ldtteam.blockui.util.SafeError; import com.ldtteam.blockui.views.*; import net.minecraft.resources.Identifier; import net.minecraft.server.packs.resources.ResourceManager; @@ -25,6 +27,7 @@ */ public final class Loader extends SimplePreparableReloadListener> { + public static final Identifier RELOADABLE_LISTEN_RES_LOC = BlockUI.resLoc("xml_loader"); public static final Loader INSTANCE = new Loader(); private final Map> paneFactories = new HashMap<>(); @@ -38,7 +41,10 @@ private Loader() register("scrollgroup", ScrollingGroup::new); register("list", ScrollingList::new); register("text", Text::new); + // Keep the legacy XML tags used by Structurize and MineColonies GUI resources. + register("label", Text::new); register("button", ButtonImage::new); + register("buttonimage", ButtonImage::new); register("toggle", ToggleButton::new); register("input", TextFieldVanilla::new); register("image", Image::new); @@ -55,17 +61,20 @@ private Loader() private static ItemIcon itemIcon(final PaneParams paneParams) { + @Deprecated(forRemoval = true, since = "26.1") + final String PARAM_PROPERTIES = "properties"; if (paneParams.hasAttribute(ItemIconWithBlockState.PARAM_NBT)) { - if (!FMLEnvironment.isProduction() && paneParams.hasAttribute(ItemIconWithProperties.PARAM_PROPERTIES)) + if (!FMLEnvironment.isProduction() && paneParams.hasAttribute(PARAM_PROPERTIES)) { - throw new IllegalStateException("Must be one of '%s' or '%s'".formatted(ItemIconWithBlockState.PARAM_NBT, ItemIconWithProperties.PARAM_PROPERTIES)); + throw new IllegalStateException("Must be one of '%s' or '%s'".formatted(ItemIconWithBlockState.PARAM_NBT, PARAM_PROPERTIES)); } return new ItemIconWithBlockState(paneParams); } - if (paneParams.hasAttribute(ItemIconWithProperties.PARAM_PROPERTIES)) + if (paneParams.hasAttribute(PARAM_PROPERTIES)) { - return new ItemIconWithProperties(paneParams); + SafeError.throwInDev(new UnsupportedOperationException("ItemIconWithProperties was not portable")); + // return new ItemIconWithProperties(paneParams); } return new ItemIcon(paneParams); } @@ -146,7 +155,7 @@ else if (parent instanceof View && params.getType().equals("window")) // layout } /** - * Parse XML contains in a ResourceLocation into contents for a Window. + * Parse XML contains in a Identifier into contents for a Window. * * @param resource xml as a {@link Identifier}. * @param parent parent view. @@ -204,7 +213,7 @@ protected Map prepare(final ResourceManager rm, final Pr } doc.getDocumentElement().normalize(); - foundXmls.put(rl, new PaneParams(doc.getDocumentElement())); + foundXmls.put(rl, new PaneParams(doc.getDocumentElement(), rl)); }); profiler.pop(); diff --git a/src/main/java/com/ldtteam/blockui/Pane.java b/src/main/java/com/ldtteam/blockui/Pane.java index ad318840..5aef03b4 100644 --- a/src/main/java/com/ldtteam/blockui/Pane.java +++ b/src/main/java/com/ldtteam/blockui/Pane.java @@ -1,13 +1,18 @@ package com.ldtteam.blockui; import com.ldtteam.blockui.controls.AbstractTextBuilder.TooltipBuilder; +import com.ldtteam.blockui.util.SafeError; +import com.ldtteam.blockui.util.cursor.Cursor; import com.ldtteam.blockui.views.View; import com.ldtteam.blockui.views.BOWindow; import com.mojang.blaze3d.platform.cursor.CursorType; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.input.KeyEvent; +import org.joml.Matrix3x2fStack; import net.minecraft.network.chat.MutableComponent; import org.jetbrains.annotations.Nullable; - import java.util.List; import java.util.Objects; @@ -31,9 +36,10 @@ public class Pane extends UiRenderMacros protected boolean visible = true; protected boolean enabled = true; protected String onHoverId = ""; - protected CursorType cursor = CursorType.DEFAULT; + protected CursorType cursor = Cursor.DEFAULT; // Runtime protected BOWindow window; + private String paneParamsPath = "UNKNOWN"; protected View parent; protected Pane hoverSource = null; /** @@ -60,6 +66,7 @@ public Pane() public Pane(final PaneParams params) { super(); + paneParamsPath = params.getXmlRelatedId(); id = params.getString("id", id); params.getScaledInteger("size", params.getParentWidth(), params.getParentHeight(), a -> { @@ -77,6 +84,8 @@ public Pane(final PaneParams params) enabled = params.getBoolean("enabled", enabled); onHoverId = params.getString("onHoverId", onHoverId); toolTipLines = params.getMultilineText("tooltip", toolTipLines); + + params.getResource("cursor", resLoc -> cursor = Cursor.of(resLoc)); } /** @@ -135,6 +144,29 @@ public final void setID(final String id) this.id = id; } + /** + * @return string path from nearest parent with id + */ + public final String getXmlRelatedId() + { + return window == null ? paneParamsPath : window.getXmlResourceLocation().toString() + "|" + Objects.requireNonNullElseGet(id, () -> pathToNearestIdParent(parent)); + } + + private static String pathToNearestIdParent(final Pane pane) + { + if (pane == null) + { + return "root"; + } + + return pane.id != null ? pane.id : pathToNearestIdParent(pane.parent) + "/" + pane.getClass().getSimpleName(); + } + + public void requireNonNull(final Object value, final String errorMessage) + { + SafeError.requireNonNull(value, errorMessage + " (" + getXmlRelatedId() + ")"); + } + /** * Set the size of a pane. * @@ -301,6 +333,22 @@ public static synchronized void setFocus(final Pane f) } } + /** + * Used mostly for overrides for default logics like {@link com.ldtteam.blockui.views.ZoomDragView#getCursor ZoomDragView} + */ + public CursorType getCursor() + { + return cursor; + } + + /** + * @param cursor use {@link Cursor} instances for default behaviour (or new instances to prevent it) + */ + public void setCursor(final CursorType cursor) + { + this.cursor = cursor; + } + /** * Draw the current Pane if visible. * @@ -315,18 +363,24 @@ public void draw(final BOGuiGraphics target, final double mx, final double my) if (shouldDraw()) { + if (wasCursorInPane && isEnabled()) + { + // intentional getter cuz overrides + target.setCursor(getCursor()); + } + drawSelf(target, mx, my); if (debugging) { final int color = wasCursorInPane ? 0xFF00FF00 : 0xFF0000FF; - target.guiGraphics().renderOutline(x, y, width, height, color); + drawLineRect(target, x, y, width, height, color); if (wasCursorInPane && !id.isEmpty()) { final int stringWidth = mc.font.width(id) + 1; - target.guiGraphics().drawString(mc.font, id, x + getWidth() - stringWidth, y + getHeight() - mc.font.lineHeight, color); + target.drawString(id, x + getWidth() - stringWidth, y + getHeight() - mc.font.lineHeight, color); } } } @@ -631,12 +685,41 @@ public boolean canHandleClick(final double mx, final double my) * @param ch the character * @param key the key * @return true if event was used or propagation needs to be stopped + * @deprecated replaced by {@link #onKeyEvent(KeyEvent)} and {@link #onCharactedEvent(CharacterEvent)} */ - public boolean onKeyTyped(final String ch, final int key) + @Deprecated(forRemoval = true, since = "26.1") + public boolean onKeyTyped(final char ch, final int key) { return false; } + /** + * Called when a key is pressed. + * + * @param keyEvent event with key, scancode and modifier keys + * @return true if event was used or propagation needs to be stopped + */ + public boolean onKeyEvent(final KeyEvent keyEvent) + { + return onKeyTyped('\0', keyEvent.key()); + } + + /** + * Called when a unicode character is emitted. + * + * @param characterEvent event with unicode codepoint + * @return true if event was used or propagation needs to be stopped + */ + public boolean onCharactedEvent(final CharacterEvent characterEvent) + { + boolean stopPropagation = false; + for (char c : Character.toChars(characterEvent.codepoint())) + { + stopPropagation |= onKeyTyped(c, -1); + } + return stopPropagation; + } + /** * On update. Can be overloaded. */ @@ -645,6 +728,13 @@ public void onUpdate() // Can be overloaded } + protected synchronized void scissorsStart(final BOGuiGraphics target) + { + // the vanilla stuff here contains transformation via current pose (baked coordination) + // if ever this stop being a thing then in git history (1.21.1) we have our version + target.enableScissor(x, y, x + width, y + height); + } + /** * X position. * @@ -665,6 +755,34 @@ public int getY() return y; } + protected synchronized void scissorsEnd(final BOGuiGraphics target) + { + final Matrix3x2fStack ms = target.pose(); + final ScreenRectangle popped = target.peekScissorStack(); + if (debugging) + { + final int color = 0xffff0000; + final int w = popped.right() - popped.left(); + final int h = popped.bottom() - popped.top(); + + final int yStart = mc.getWindow().getHeight() - popped.bottom(); + + ms.pushMatrix(); + ms.identity(); + drawLineRect(target, popped.left(), yStart, w, h, color, 2); + + final String scId = "scissor_" + (id.isEmpty() ? this.toString() : id); + final int stringWidth = mc.font.width(scId) + 1; + target.drawString(scId, + popped.left() + w - stringWidth, + yStart + h - 2 * mc.font.lineHeight, + color); + ms.popMatrix(); + } + + target.disableScissor(); + } + /** * Wheel input. * @@ -757,11 +875,12 @@ public Pane getHoverPane() * * @param mx mouse start x * @param my mouse start y + * @param speed drag speed * @param deltaX relative x * @param deltaY relative y * @return true if event was used or propagation needs to be stopped */ - public boolean onMouseDrag(final double mx, final double my, final double deltaX, final double deltaY) + public boolean onMouseDrag(final double mx, final double my, final int speed, final double deltaX, final double deltaY) { return false; } diff --git a/src/main/java/com/ldtteam/blockui/PaneParams.java b/src/main/java/com/ldtteam/blockui/PaneParams.java index 7e35829c..363cae23 100644 --- a/src/main/java/com/ldtteam/blockui/PaneParams.java +++ b/src/main/java/com/ldtteam/blockui/PaneParams.java @@ -1,8 +1,12 @@ package com.ldtteam.blockui; import com.ldtteam.blockui.mod.Log; +import com.ldtteam.blockui.util.SafeError; import com.ldtteam.blockui.views.View; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.resources.Identifier; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.TagParser; import net.minecraft.network.chat.MutableComponent; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; @@ -21,15 +25,17 @@ public class PaneParams private final List children; private final Node node; private View parentView; + private final Identifier windowResLoc; /** * Instantiates the pane parameters. * * @param n the node. */ - public PaneParams(final Node n) + public PaneParams(final Node n, final Identifier windowResLoc) { node = n; + this.windowResLoc = windowResLoc; children = new ArrayList<>(node.getChildNodes().getLength()); } @@ -112,7 +118,7 @@ public List getChildren() { if (child.getNodeType() == Node.ELEMENT_NODE) { - children.add(new PaneParams(child)); + children.add(new PaneParams(child, windowResLoc)); } child = child.getNextSibling(); } @@ -169,6 +175,45 @@ public T getProperty(String name, Function parser, T def) return result != null ? result : def; } + /** + * Get the compoundTag attribute. + * + * @param name the name to search. + * @return the attribute. + */ + @Nullable + public CompoundTag getCompoundTag(final String name) + { + return getCompoundTag(name, null); + } + + /** + * Get the compoundTag attribute from the name and revert to the default if not present. + * + * @param name the name. + * @param def the default value if none can be found + * @return the String. + */ + public CompoundTag getCompoundTag(final String name, final CompoundTag def) + { + final String data = getString(name, null); + if (data == null) + { + return def; + } + CompoundTag tag; + try + { + tag = TagParser.parseCompoundFully(data); + } + catch (CommandSyntaxException e) + { + SafeError.throwInDev(new IllegalArgumentException("Failed to parse compound at: " + getXmlRelatedId(), e)); + return def; + } + return tag; + } + /** * Get the string attribute. * @@ -403,4 +448,25 @@ public String hasAnyAttribute(final String def, final String... attributes) } return def; } + + /** + * @return string path from nearest parent with id + */ + public String getXmlRelatedId() + { + return windowResLoc.toString() + "|" + Objects.requireNonNullElseGet(getString("id"), () -> pathToNearestIdParent(node)); + } + + private static String pathToNearestIdParent(final Node node) + { + if (node == null) + { + return "root"; + } + + final NamedNodeMap attributes = node.getAttributes(); + final Node idNode = attributes == null ? null : attributes.getNamedItem("id"); + final String id = idNode == null ? null : idNode.getNodeValue(); + return id != null ? id : pathToNearestIdParent(node.getParentNode()) + "/" + node.getLocalName(); + } } diff --git a/src/main/java/com/ldtteam/blockui/UiRenderMacros.java b/src/main/java/com/ldtteam/blockui/UiRenderMacros.java new file mode 100644 index 00000000..8be68fbe --- /dev/null +++ b/src/main/java/com/ldtteam/blockui/UiRenderMacros.java @@ -0,0 +1,915 @@ +package com.ldtteam.blockui; + +import com.ldtteam.blockui.mod.BlockUI; +import com.ldtteam.blockui.util.color.IColour; +import com.mojang.blaze3d.PrimitiveTopology; +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.TextureSetup; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.client.renderer.state.gui.GuiElementRenderState; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling; +import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling.NineSlice; +import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling.Tile; +import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling.Type; +import net.minecraft.resources.Identifier; +import net.minecraft.util.Mth; +import net.neoforged.fml.loading.FMLEnvironment; +import org.joml.Matrix3x2f; +import org.jspecify.annotations.Nullable; +import java.util.Objects; +import java.util.function.BiConsumer; + +/** + * Our replacement for GuiComponent. + */ +public class UiRenderMacros +{ + public static final double HALF_BIAS = 0.5; + /** alpha/blending enabled by default */ + public static final RenderPipeline GUI_POS_COLOR_TRIANGLES = RenderPipeline.builder(RenderPipelines.GUI_SNIPPET) + .withLocation(BlockUI.resLoc("gui_pos_color_triangles")) + .withVertexShader("core/position_color") + .withFragmentShader("core/position_color") + .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR) + .withPrimitiveTopology(PrimitiveTopology.TRIANGLES) + .build(); + /** alpha/blending enabled by default */ + public static final RenderPipeline GUI_POS_TEX_TRIANGLES = RenderPipeline.builder(RenderPipelines.GUI_TEXTURED_SNIPPET) + .withLocation(BlockUI.resLoc("gui_pos_tex_triangles")) + .withVertexShader("core/position_tex") + .withFragmentShader("core/position_tex") + .withVertexBinding(0, DefaultVertexFormat.POSITION_TEX) + .withPrimitiveTopology(PrimitiveTopology.TRIANGLES) + .build(); + /** alpha/blending enabled by default */ + public static final RenderPipeline GUI_POS_TEX_COLOR_TRIANGLES = RenderPipeline.builder(RenderPipelines.GUI_TEXTURED_SNIPPET) + .withLocation(BlockUI.resLoc("gui_pos_tex_color_triangles")) + .withVertexShader("core/position_tex_color") + .withFragmentShader("core/position_tex_color") + .withVertexBinding(0, DefaultVertexFormat.POSITION_TEX_COLOR) + .withPrimitiveTopology(PrimitiveTopology.TRIANGLES) + .build(); + /** alpha/blending enabled by default */ + public static final RenderPipeline GUI_POS_COLOR_LINES = RenderPipeline.builder(RenderPipelines.GUI_SNIPPET) + .withLocation(BlockUI.resLoc("gui_pos_color_lines")) + .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR) + .withPrimitiveTopology(PrimitiveTopology.LINES) + .build(); + + public static void drawLineRectGradient(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int w, + final int h, + final int argbColorStart, + final int argbColorEnd) + { + drawLineRectGradient(ps, x, y, w, h, argbColorStart, argbColorEnd, 1); + } + + public static void drawLineRectGradient(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int w, + final int h, + final int argbColorStart, + final int argbColorEnd, + final int lineWidth) + { + drawLineRectGradient(ps, + x, + y, + w, + h, + (argbColorStart >> 16) & 0xff, + (argbColorEnd >> 16) & 0xff, + (argbColorStart >> 8) & 0xff, + (argbColorEnd >> 8) & 0xff, + argbColorStart & 0xff, + argbColorEnd & 0xff, + (argbColorStart >> 24) & 0xff, + (argbColorEnd >> 24) & 0xff, + lineWidth); + } + + public static void drawLineRectGradient(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int w, + final int h, + final int redStart, + final int redEnd, + final int greenStart, + final int greenEnd, + final int blueStart, + final int blueEnd, + final int alphaStart, + final int alphaEnd, + final int lineWidth) + { + if (lineWidth < 1 || (alphaStart == 0 && alphaEnd == 0)) + { + return; + } + + submitNoTex(ps, GUI_POS_COLOR_TRIANGLES, x, y, w, h, (m, buffer) -> { + populateFillTriangles(m, buffer, x, y, w, lineWidth, redStart, greenStart, blueStart, alphaStart); + populateFillGradientTriangles(m, + buffer, + x, + y + lineWidth, + lineWidth, + h - 2 * lineWidth, + redStart, + redEnd, + greenStart, + greenEnd, + blueStart, + blueEnd, + alphaStart, + alphaEnd); + populateFillGradientTriangles(m, + buffer, + x + w - lineWidth, + y + lineWidth, + lineWidth, + h - 2 * lineWidth, + redStart, + redEnd, + greenStart, + greenEnd, + blueStart, + blueEnd, + alphaStart, + alphaEnd); + populateFillTriangles(m, buffer, x, y + h - lineWidth, w, lineWidth, redEnd, greenEnd, blueEnd, alphaEnd); + }); + } + + public static void drawLineRect(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int w, + final int h, + final int argbColor) + { + drawLineRect(ps, x, y, w, h, argbColor, 1); + } + + public static void drawLineRect(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int w, + final int h, + final int argbColor, + final int lineWidth) + { + drawLineRect(ps, + x, + y, + w, + h, + (argbColor >> 16) & 0xff, + (argbColor >> 8) & 0xff, + argbColor & 0xff, + (argbColor >> 24) & 0xff, + lineWidth); + } + + public static void drawLineRect(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int w, + final int h, + final int red, + final int green, + final int blue, + final int alpha, + final int lineWidth) + { + if (lineWidth < 1 || alpha == 0) + { + return; + } + + submitNoTex(ps, GUI_POS_COLOR_TRIANGLES, x, y, w, h, (m, buffer) -> { + populateFillTriangles(m, buffer, x, y, w, lineWidth, red, green, blue, alpha); + populateFillTriangles(m, buffer, x, y + lineWidth, lineWidth, h - 2 * lineWidth, red, green, blue, alpha); + populateFillTriangles(m, buffer, x + w - lineWidth, y + lineWidth, lineWidth, h - 2 * lineWidth, red, green, blue, alpha); + populateFillTriangles(m, buffer, x, y + h - lineWidth, w, lineWidth, red, green, blue, alpha); + }); + } + + public static void fill(final GuiGraphicsExtractor ps, final int x, final int y, final int w, final int h, final int argbColor) + { + fill(ps, x, y, w, h, (argbColor >> 16) & 0xff, (argbColor >> 8) & 0xff, argbColor & 0xff, (argbColor >> 24) & 0xff); + } + + public static void fill(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int w, + final int h, + final int red, + final int green, + final int blue, + final int alpha) + { + if (alpha == 0) + { + return; + } + + submitNoTex(ps, + GUI_POS_COLOR_TRIANGLES, + x, + y, + w, + h, + (m, buffer) -> populateFillTriangles(m, buffer, x, y, w, h, red, green, blue, alpha)); + } + + public static void fillGradient(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int w, + final int h, + final int argbColorStart, + final int argbColorEnd) + { + fillGradient(ps, + x, + y, + w, + h, + (argbColorStart >> 16) & 0xff, + (argbColorEnd >> 16) & 0xff, + (argbColorStart >> 8) & 0xff, + (argbColorEnd >> 8) & 0xff, + argbColorStart & 0xff, + argbColorEnd & 0xff, + (argbColorStart >> 24) & 0xff, + (argbColorEnd >> 24) & 0xff); + } + + public static void fillGradient(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int w, + final int h, + final int redStart, + final int redEnd, + final int greenStart, + final int greenEnd, + final int blueStart, + final int blueEnd, + final int alphaStart, + final int alphaEnd) + { + if (alphaStart == 0 && alphaEnd == 0) + { + return; + } + + submitNoTex(ps, + GUI_POS_COLOR_TRIANGLES, + x, + y, + w, + h, + (m, buffer) -> populateFillGradientTriangles(m, + buffer, + x, + y, + w, + h, + redStart, + redEnd, + greenStart, + greenEnd, + blueStart, + blueEnd, + alphaStart, + alphaEnd)); + } + + public static void hLine(final GuiGraphicsExtractor ps, final int x, final int xEnd, final int y, final int argbColor) + { + line(ps, x, y, xEnd, y, (argbColor >> 16) & 0xff, (argbColor >> 8) & 0xff, argbColor & 0xff, (argbColor >> 24) & 0xff); + } + + public static void hLine(final GuiGraphicsExtractor ps, + final int x, + final int xEnd, + final int y, + final int red, + final int green, + final int blue, + final int alpha) + { + line(ps, x, y, xEnd, y, red, green, blue, alpha); + } + + public static void vLine(final GuiGraphicsExtractor ps, final int x, final int y, final int yEnd, final int argbColor) + { + line(ps, x, y, x, yEnd, (argbColor >> 16) & 0xff, (argbColor >> 8) & 0xff, argbColor & 0xff, (argbColor >> 24) & 0xff); + } + + public static void vLine(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int yEnd, + final int red, + final int green, + final int blue, + final int alpha) + { + line(ps, x, y, x, yEnd, red, green, blue, alpha); + } + + public static void line(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int xEnd, + final int yEnd, + final int argbColor) + { + line(ps, x, y, xEnd, yEnd, (argbColor >> 16) & 0xff, (argbColor >> 8) & 0xff, argbColor & 0xff, (argbColor >> 24) & 0xff); + } + + public static void line(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int xEnd, + final int yEnd, + final int red, + final int green, + final int blue, + final int alpha) + { + if (alpha == 0) + { + return; + } + + submitNoTex(ps, GUI_POS_COLOR_LINES, x, y, xEnd - x, yEnd - y, (m, buffer) -> { + buffer.addVertexWith2DPose(m, x, y).setColor(red, green, blue, alpha); + buffer.addVertexWith2DPose(m, xEnd, yEnd).setColor(red, green, blue, alpha); + }); + } + + public static void blit(final GuiGraphicsExtractor ps, + final Identifier rl, + final int x, + final int y, + final int w, + final int h, + final int u, + final int v, + final int mapW, + final int mapH) + { + blit(ps, rl, x, y, w, h, (float) u / mapW, (float) v / mapH, (float) (u + w) / mapW, (float) (v + h) / mapH, null); + } + + public static void blit(final GuiGraphicsExtractor ps, + final Identifier rl, + final int x, + final int y, + final int w, + final int h, + final int u, + final int v, + final int uW, + final int vH, + final int mapW, + final int mapH) + { + blit(ps, rl, x, y, w, h, (float) u / mapW, (float) v / mapH, (float) (u + uW) / mapW, (float) (v + vH) / mapH, null); + } + + public static void blitSprite(final GuiGraphicsExtractor ps, + final TextureAtlasSprite sprite, + final GuiSpriteScaling guiScaling, + final int x, + final int y, + final int w, + final int h) + { + resolveSprite(sprite, guiScaling).blit(ps, x, y, w, h); + } + + public static void blitSprite(final GuiGraphicsExtractor ps, + final TextureAtlasSprite sprite, + final int x, + final int y, + final int w, + final int h) + { + blit(ps, sprite.atlasLocation(), x, y, w, h, sprite.getU0(), sprite.getV0(), sprite.getU1(), sprite.getV1(), null); + } + + public static void blit(final GuiGraphicsExtractor ps, + final Identifier rl, + final int x, + final int y, + final int w, + final int h, + @Nullable final IColour colorModulation) + { + blit(ps, rl, x, y, w, h, 0.0f, 0.0f, 1.0f, 1.0f, colorModulation); + } + + public static void blit(final GuiGraphicsExtractor ps, + final Identifier rl, + final int x, + final int y, + final int w, + final int h, + final float uMin, + final float vMin, + final float uMax, + final float vMax, + @Nullable final IColour colorModulation) + { + if (colorModulation == null) + { + submitBlit(ps, + GUI_POS_TEX_TRIANGLES, + x, + y, + w, + h, + rl, + (m, buffer) -> populateBlitTriangles(buffer, m, x, x + w, y, y + h, uMin, uMax, vMin, vMax)); + } + else + { + // TODO: this would normally use uniform 'colorModulator', but vanilla doesn't expose it to gui yet + submitBlit(ps, + GUI_POS_TEX_COLOR_TRIANGLES, + x, + y, + w, + h, + rl, + (m, buffer) -> populateBlitTriangles(buffer, m, x, x + w, y, y + h, uMin, uMax, vMin, vMax, colorModulation)); + } + } + + /** + * Draws texture without scaling so one texel is one pixel, using repeatable texture center. + * + * @param ps MatrixStack + * @param rl image ResLoc + * @param x start target coords [pixels] + * @param y start target coords [pixels] + * @param width target rendering box [pixels] + * @param height target rendering box [pixels] + * @param uMin texture start offset [normalized texels] + * @param vMin texture start offset [normalized texels] + * @param uMax texture end offset [normalized texels] + * @param vMax texture end offset [normalized texels] + * @param nineSlice repeatable box definition [texels] + * @param colorModulation texture color modulation + */ + public static void blitRepeatable(final GuiGraphicsExtractor ps, + final Identifier rl, + final int x, + final int y, + final int width, + final int height, + final float uMin, + final float vMin, + final float uMax, + final float vMax, + final NineSlice nineSlice, + final IColour colorModulation) + { + if (nineSlice.border().left() < 0 || nineSlice.border().right() < 0 || + nineSlice.border().top() < 0 || + nineSlice.border().bottom() < 0) + { + throw new IllegalArgumentException("Negative nineSlice borders"); + } + if (nineSlice.border().left() + nineSlice.border().right() > nineSlice.width() || + nineSlice.border().top() + nineSlice.border().bottom() > nineSlice.height()) + { + throw new IllegalArgumentException("NineSlice borders greater than box"); + } + + if (nineSlice.width() == width && nineSlice.height() == height) + { + blit(ps, rl, x, y, width, height, uMin, vMin, uMax, vMax, colorModulation); + return; + } + + submitBlit(ps, GUI_POS_TEX_TRIANGLES, x, y, width, height, rl, (m, b) -> { + final IColour c = Objects.requireNonNullElse(colorModulation, NOOP_COLOUR); + + // nineSlice w/h is in UV [0,1] + // nineSlice assumes texel = pixel + + final int lrBorder = nineSlice.border().left() + nineSlice.border().right(); + final int tbBorder = nineSlice.border().top() + nineSlice.border().bottom(); + + // pixels + final int xAdjust = nineSlice.border().left(); + final int yAdjust = nineSlice.border().top(); + final int pixWidth = nineSlice.width() - lrBorder; + final int pixHeight = nineSlice.height() - tbBorder; + + final int repeatCountX = Math.max(0, width - lrBorder) / pixWidth; + final int repeatCountY = Math.max(0, height - tbBorder) / pixHeight; + + // corners + + final int x0 = x; + final int x1 = x + xAdjust; + final int x2normal = xAdjust + repeatCountX * pixWidth; + final int x2stretched = width - xAdjust; + final int x2 = x + (nineSlice.stretchInner() ? x2stretched : x2normal); + final int x3 = x + width; + + final int y0 = y; + final int y1 = y + yAdjust; + final int y2normal = yAdjust + repeatCountY * pixHeight; + final int y2stretched = height - yAdjust; + final int y2 = y + (nineSlice.stretchInner() ? y2stretched : y2normal); + final int y3 = y + height; + + final float u0 = uMin; + final float u1 = Mth.lerp((float) nineSlice.border().left() / nineSlice.width(), uMin, uMax); + final float u2stretchFix = nineSlice.stretchInner() ? 0 : x2stretched - x2normal; + final float u2 = Mth.lerp(1.0f - (nineSlice.border().right() + u2stretchFix) / nineSlice.width(), uMin, uMax); + final float u3 = uMax; + + final float v0 = vMin; + final float v1 = Mth.lerp((float) nineSlice.border().top() / nineSlice.height(), vMin, vMax); + final float v2stretchFix = nineSlice.stretchInner() ? 0 : y2stretched - y2normal; + final float v2 = Mth.lerp(1.0f - (nineSlice.border().bottom() + v2stretchFix) / nineSlice.height(), vMin, vMax); + final float v3 = vMax; + + populateBlitTriangles(b, m, x0, x1, y0, y1, u0, u1, v0, v1, c); + populateBlitTriangles(b, m, x0, x1, y2, y3, u0, u1, v2, v3, c); + populateBlitTriangles(b, m, x2, x3, y0, y1, u2, u3, v0, v1, c); + populateBlitTriangles(b, m, x2, x3, y2, y3, u2, u3, v2, v3, c); + + // tiles + + final float uS = u1; + final float uE = Mth.lerp(1.0f - (float) nineSlice.border().right() / nineSlice.width(), uMin, uMax); + final float vS = v1; + final float vE = Mth.lerp(1.0f - (float) nineSlice.border().bottom() / nineSlice.height(), vMin, vMax); + + // stretch single tile + if (nineSlice.stretchInner()) + { + final int xS = x1, xE = x2; + final int yS = y1, yE = y2; + + // in same order as fori + populateBlitTriangles(b, m, xS, xE, y0, y1, uS, uE, v0, v1, c); + populateBlitTriangles(b, m, xS, xE, y2, y3, uS, uE, v2, v3, c); + + populateBlitTriangles(b, m, xS, xE, yS, yE, uS, uE, vS, vE, c); + + populateBlitTriangles(b, m, x0, x1, yS, yE, u0, u1, vS, vE, c); + populateBlitTriangles(b, m, x2, x3, yS, yE, u2, u3, vS, vE, c); + return; + } + // else draw tiling + + // center and top & bot edges + for (int i = 0; i < repeatCountX; i++) + { + final int xS = x1 + i * pixWidth; + final int xE = xS + pixWidth; + + populateBlitTriangles(b, m, xS, xE, y0, y1, uS, uE, v0, v1, c); + populateBlitTriangles(b, m, xS, xE, y2, y3, uS, uE, v2, v3, c); + + for (int j = 0; j < repeatCountY; j++) + { + final int yS = y1 + j * pixHeight; + final int yE = yS + pixHeight; + + populateBlitTriangles(b, m, xS, xE, yS, yE, uS, uE, vS, vE, c); + } + } + + // left & right edges + for (int j = 0; j < repeatCountY; j++) + { + final int yS = y1 + j * pixHeight; + final int yE = yS + pixHeight; + + populateBlitTriangles(b, m, x0, x1, yS, yE, u0, u1, vS, vE, c); + populateBlitTriangles(b, m, x2, x3, yS, yE, u2, u3, vS, vE, c); + } + }); + } + + public static void populateFillTriangles(final Matrix3x2f m, + final VertexConsumer buffer, + final int x, + final int y, + final int w, + final int h, + final int red, + final int green, + final int blue, + final int alpha) + { + if (w == 0 || h == 0) + { + return; + } + + buffer.addVertexWith2DPose(m, x, y).setColor(red, green, blue, alpha); + buffer.addVertexWith2DPose(m, x, y + h).setColor(red, green, blue, alpha); + buffer.addVertexWith2DPose(m, x + w, y).setColor(red, green, blue, alpha); + buffer.addVertexWith2DPose(m, x + w, y).setColor(red, green, blue, alpha); + buffer.addVertexWith2DPose(m, x, y + h).setColor(red, green, blue, alpha); + buffer.addVertexWith2DPose(m, x + w, y + h).setColor(red, green, blue, alpha); + } + + public static void populateFillGradientTriangles(final Matrix3x2f m, + final VertexConsumer buffer, + final int x, + final int y, + final int w, + final int h, + final int redStart, + final int redEnd, + final int greenStart, + final int greenEnd, + final int blueStart, + final int blueEnd, + final int alphaStart, + final int alphaEnd) + { + if (w == 0 || h == 0) + { + return; + } + + buffer.addVertexWith2DPose(m, x, y).setColor(redStart, greenStart, blueStart, alphaStart); + buffer.addVertexWith2DPose(m, x, y + h).setColor(redEnd, greenEnd, blueEnd, alphaEnd); + buffer.addVertexWith2DPose(m, x + w, y).setColor(redStart, greenStart, blueStart, alphaStart); + buffer.addVertexWith2DPose(m, x + w, y).setColor(redStart, greenStart, blueStart, alphaStart); + buffer.addVertexWith2DPose(m, x, y + h).setColor(redEnd, greenEnd, blueEnd, alphaEnd); + buffer.addVertexWith2DPose(m, x + w, y + h).setColor(redEnd, greenEnd, blueEnd, alphaEnd); + } + + public static void populateBlitTriangles(final VertexConsumer buffer, + final Matrix3x2f mat, + final float xStart, + final float xEnd, + final float yStart, + final float yEnd, + final float uMin, + final float uMax, + final float vMin, + final float vMax) + { + if (xStart == xEnd || yStart == yEnd) + { + return; + } + + buffer.addVertexWith2DPose(mat, xStart, yStart).setUv(uMin, vMin); + buffer.addVertexWith2DPose(mat, xStart, yEnd).setUv(uMin, vMax); + buffer.addVertexWith2DPose(mat, xEnd, yStart).setUv(uMax, vMin); + buffer.addVertexWith2DPose(mat, xEnd, yStart).setUv(uMax, vMin); + buffer.addVertexWith2DPose(mat, xStart, yEnd).setUv(uMin, vMax); + buffer.addVertexWith2DPose(mat, xEnd, yEnd).setUv(uMax, vMax); + } + + public static void populateBlitTriangles(final VertexConsumer buffer, + final Matrix3x2f mat, + final float xStart, + final float xEnd, + final float yStart, + final float yEnd, + final float uMin, + final float uMax, + final float vMin, + final float vMax, + final IColour color) + { + if (xStart == xEnd || yStart == yEnd) + { + return; + } + + buffer.addVertexWith2DPose(mat, xStart, yStart).setUv(uMin, vMin); + color.writeIntoBuffer(buffer); + buffer.addVertexWith2DPose(mat, xStart, yEnd).setUv(uMin, vMax); + color.writeIntoBuffer(buffer); + buffer.addVertexWith2DPose(mat, xEnd, yStart).setUv(uMax, vMin); + color.writeIntoBuffer(buffer); + buffer.addVertexWith2DPose(mat, xEnd, yStart).setUv(uMax, vMin); + color.writeIntoBuffer(buffer); + buffer.addVertexWith2DPose(mat, xStart, yEnd).setUv(uMin, vMax); + color.writeIntoBuffer(buffer); + buffer.addVertexWith2DPose(mat, xEnd, yEnd).setUv(uMax, vMax); + color.writeIntoBuffer(buffer); + } + + /** + * @return rendering lambda detached from sprite and guiScaling instances + */ + public static ResolvedBlit resolveSprite(final TextureAtlasSprite sprite, final GuiSpriteScaling guiScaling) + { + final Identifier atlasLocation = sprite.atlasLocation(); + final float u0 = sprite.getU0(); + final float v0 = sprite.getV0(); + final float u1 = sprite.getU1(); + final float v1 = sprite.getV1(); + if (guiScaling.type() == Type.STRETCH) + { + return (ps, x, y, w, h, c) -> blit(ps, atlasLocation, x, y, w, h, u0, v0, u1, v1, c); + } + else if (guiScaling instanceof final NineSlice nineSlice) + { + return (ps, x, y, w, h, c) -> blitRepeatable(ps, atlasLocation, x, y, w, h, u0, v0, u1, v1, nineSlice, c); + } + else if (guiScaling instanceof final Tile tile) + { + final NineSlice nineSlice = new NineSlice(tile.width(), tile.height(), new NineSlice.Border(0, 0, 0, 0), false); + return (ps, x, y, w, h, c) -> blitRepeatable(ps, atlasLocation, x, y, w, h, u0, v0, u1, v1, nineSlice, c); + } + if (!FMLEnvironment.isProduction()) + { + throw new UnsupportedOperationException("Missing resolver for gui scaling: " + guiScaling.type()); + } + return ResolvedBlit.EMPTY; + } + + /** + * Used for precompiling math around rendering + */ + @FunctionalInterface + public static interface ResolvedBlit + { + public static final ResolvedBlit EMPTY = (ps, x, y, w, h, c) -> {}; + + void blit(GuiGraphicsExtractor ps, int x, int y, int w, int h, @Nullable IColour colorModulation); + + default void blit(final GuiGraphicsExtractor ps, final int x, final int y, final int w, final int h) + { + blit(ps, x, y, w, h, colorModulation()); + } + + @Nullable + default IColour colorModulation() + { + return null; + } + + default ResolvedBlitWithColorModulation withColorModulation(final IColour colorModulation) + { + return new ResolvedBlitWithColorModulation(this, colorModulation); + } + } + + public static record ResolvedBlitWithColorModulation(ResolvedBlit blit, IColour colorModulation) implements ResolvedBlit + { + @Override + public void blit(final GuiGraphicsExtractor ps, + final int x, + final int y, + final int w, + final int h, + @Nullable final IColour colorModulation) + { + blit.blit(ps, x, y, w, h, colorModulation); + } + } + + public static void submitNoTex(final GuiGraphicsExtractor target, + final RenderPipeline pipeline, + final int x, + final int y, + final int w, + final int h, + final BiConsumer task) + { + innerSubmit(target, + x, + y, + w, + h, + (pose, bounds, scissors) -> target.submitGuiElementRenderState( + new UiRenderMacrosGuiElementRenderState(pose, task, pipeline, TextureSetup.noTexture(), bounds, scissors))); + } + + public static void submitBlit(final GuiGraphicsExtractor target, + final RenderPipeline pipeline, + final int x, + final int y, + final int w, + final int h, + final Identifier texResLoc, + final BiConsumer task) + { + innerSubmit(target, x, y, w, h, (pose, bounds, scissors) -> { + final AbstractTexture texture = target.minecraft.getTextureManager().getTexture(texResLoc); + final TextureSetup textureSetup = TextureSetup.singleTexture(texture.getTextureView(), texture.getSampler()); + target.submitGuiElementRenderState( + new UiRenderMacrosGuiElementRenderState(pose, task, pipeline, textureSetup, bounds, scissors)); + }); + } + + public static void innerSubmit(final GuiGraphicsExtractor target, + final int x, + final int y, + final int w, + final int h, + final SubmitTask task) + { + final Matrix3x2f pose = new Matrix3x2f(target.pose()); + final ScreenRectangle scissors = target.peekScissorStack(); + + ScreenRectangle bounds = new ScreenRectangle(x, y, w, h); + bounds = bounds.transformMaxBounds(pose); + bounds = scissors == null ? bounds : scissors.intersection(bounds); + + if (bounds != null) + { + task.submit(pose, bounds, scissors); + } + } + + @FunctionalInterface + public static interface SubmitTask + { + void submit(Matrix3x2f pose, ScreenRectangle bounds, ScreenRectangle scissors); + } + + public record UiRenderMacrosGuiElementRenderState(Matrix3x2f pose, + BiConsumer task, + RenderPipeline pipeline, + TextureSetup textureSetup, + @Nullable ScreenRectangle bounds, + @Nullable ScreenRectangle scissorArea) implements GuiElementRenderState + { + @Override + public void buildVertices(final VertexConsumer vertexConsumer) + { + task.accept(pose(), vertexConsumer); + } + } + + public static final IColour NOOP_COLOUR = new IColour() + { + @Override + public int red() + { + return 0; + } + + @Override + public int green() + { + return 0; + } + + @Override + public int blue() + { + return 0; + } + + @Override + public int alpha() + { + return 0; + } + + @Override + public int argb() + { + return 0; + } + + @Override + public int rgba() + { + return 0; + } + + @Override + public void writeIntoBuffer(VertexConsumer buffer) + { + // intentionally skip + } + }; +} diff --git a/src/main/java/com/ldtteam/blockui/controls/AbstractTextBuilder.java b/src/main/java/com/ldtteam/blockui/controls/AbstractTextBuilder.java index e06f2071..10ba851f 100644 --- a/src/main/java/com/ldtteam/blockui/controls/AbstractTextBuilder.java +++ b/src/main/java/com/ldtteam/blockui/controls/AbstractTextBuilder.java @@ -23,6 +23,7 @@ public abstract class AbstractTextBuilder

getText() /** * Finishes current paragraph and replaces text of given pane. - * + * * @see #paragraphBreak() */ public R applyToPane(final AbstractTextElement textPane) @@ -411,7 +512,7 @@ public R applyToPane(final AbstractTextElement textPane) /** * Finishes current paragraph and appends to current text of given pane. - * + * * @see #paragraphBreak() */ public R appendToPane(final AbstractTextElement textPane) diff --git a/src/main/java/com/ldtteam/blockui/controls/AbstractTextElement.java b/src/main/java/com/ldtteam/blockui/controls/AbstractTextElement.java index a75dadff..9e8c30a6 100644 --- a/src/main/java/com/ldtteam/blockui/controls/AbstractTextElement.java +++ b/src/main/java/com/ldtteam/blockui/controls/AbstractTextElement.java @@ -8,18 +8,11 @@ import com.ldtteam.blockui.util.SpacerTextComponent.FormattedSpacerComponent; import com.ldtteam.blockui.util.ToggleableTextComponent; import com.ldtteam.blockui.util.ToggleableTextComponent.FormattedToggleableCharSequence; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.gui.Font; -import net.minecraft.client.renderer.MultiBufferSource; +import org.joml.Matrix3x2fStack; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.util.FormattedCharSequence; -import net.neoforged.neoforge.client.NeoForgeRenderTypes; import org.jetbrains.annotations.Nullable; -import org.joml.Matrix3x2fStack; -import org.joml.Matrix4f; -import org.joml.Vector4f; import java.util.Collections; import java.util.List; @@ -36,7 +29,7 @@ public abstract class AbstractTextElement extends Pane public static final double DEFAULT_TEXT_SCALE = 1.0d; public static final Alignment DEFAULT_TEXT_ALIGNMENT = Alignment.MIDDLE_LEFT; - public static final int DEFAULT_TEXT_COLOR = 0xffffff; // white + public static final int DEFAULT_TEXT_COLOR = 0xffffffff; // white public static final boolean DEFAULT_TEXT_SHADOW = false; public static final boolean DEFAULT_TEXT_WRAP = false; public static final int DEFAULT_TEXT_LINESPACE = 0; @@ -305,25 +298,10 @@ else if (textAlignment.isVerticalCentered()) offsetY += (textHeight - renderedTextHeight) / 2; } - ms.pushPose(); + ms.pushMatrix(); ms.translate(x + offsetX, y + offsetY); ms.scale((float) textScale, (float) textScale); - final Matrix4f matrix4f = ms.last().pose(); - - // we want to see how big is one scaled pixel on monitor (using one texel) - final int fbW = window.getScreen().getFramebufferWidth(), fbH = window.getScreen().getFramebufferHeight(); - final Vector4f temp = new Vector4f(1, 1, 0, 0); - matrix4f.transform(temp); // PVM - temp.w = 1; // vector -> point - temp.mulProject(RenderSystem.getProjectionMatrix()); // projection, perspective - temp.add(1, 1, 0, 0); // viewport, discard non (x,y) - temp.mul(fbW / 2.0f, fbH / 2.0f, 0, 0); - - final float scale = temp.distanceSquared(FILTERING_THRESHOLD, fbH - FILTERING_THRESHOLD, 0, 0); - NeoForgeRenderTypes.enableTextTextureLinearFiltering = Math.abs(temp.x - fbH + temp.y) > FILTERING_THRESHOLD || scale < FILTERING_MAX_SCALE * FILTERING_MAX_SCALE; - - final MultiBufferSource.BufferSource drawBuffer = target.bufferSource(); int lineShift = 0; for (FormattedCharSequence row : preparedText) { @@ -361,15 +339,11 @@ else if (textAlignment.isHorizontalCentered()) xOffset = 0; } - mc.font.drawInBatch(row, xOffset, lineShift, color, textShadow, matrix4f, drawBuffer, Font.DisplayMode.NORMAL, 0, 15728880); + target.text(mc.font, row, xOffset, lineShift, color, textShadow); lineShift += mc.font.lineHeight + textLinespace; } - drawBuffer.endBatch(); - - NeoForgeRenderTypes.enableTextTextureLinearFiltering = false; - RenderSystem.disableBlend(); - ms.popPose(); + ms.popMatrix(); } public Alignment getTextAlignment() diff --git a/src/main/java/com/ldtteam/blockui/controls/ButtonImage.java b/src/main/java/com/ldtteam/blockui/controls/ButtonImage.java index 6840c502..7cc1fd1f 100644 --- a/src/main/java/com/ldtteam/blockui/controls/ButtonImage.java +++ b/src/main/java/com/ldtteam/blockui/controls/ButtonImage.java @@ -4,15 +4,12 @@ import com.ldtteam.blockui.BOGuiGraphics; import com.ldtteam.blockui.PaneParams; import com.ldtteam.blockui.Parsers; -import com.mojang.blaze3d.systems.RenderSystem; +import com.ldtteam.blockui.util.texture.ResolvedWidgetSprites; import net.minecraft.client.gui.components.AbstractButton; import net.minecraft.client.gui.components.WidgetSprites; -import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; import net.minecraft.resources.Identifier; -import net.minecraft.util.ARGB; import net.minecraft.util.Mth; -import net.neoforged.fml.loading.FMLEnvironment; import java.util.Objects; @@ -28,11 +25,13 @@ public class ButtonImage extends Button */ public static final int DEFAULT_BUTTON_WIDTH = 200; public static final int DEFAULT_BUTTON_HEIGHT = 20; - public static final int DEFAULT_ENABLED_COLOR = 0xFFFFFF; - public static final int DEFAULT_HOVER_COLOR = 0xFFFFA0; - public static final int DEFAULT_DISABLED_COLOR = 0xA0A0A0; + public static final int DEFAULT_ENABLED_COLOR = 0xFFFFFFFF; + @Deprecated(forRemoval = true, since = "26.1") // not used in vanilla anymore + public static final int DEFAULT_HOVER_COLOR = 0xFFFFFFA0; + public static final int DEFAULT_DISABLED_COLOR = 0xFFA0A0A0; protected WidgetSprites textures = VANILLA_BUTTON; + protected ResolvedWidgetSprites resolvedTextures = null; /** * Default constructor. Makes a small square button. @@ -91,7 +90,7 @@ public void setVanillaButton() } textures = VANILLA_BUTTON; textColor = DEFAULT_ENABLED_COLOR; - textHoverColor = DEFAULT_HOVER_COLOR; + textHoverColor = DEFAULT_ENABLED_COLOR; textDisabledColor = DEFAULT_DISABLED_COLOR; textOffsetX = 3; textOffsetY = 3; @@ -125,13 +124,16 @@ private void loadTextInfo(final PaneParams params) recalcTextRendering(); } - + /** * @param buttonTextures group of all possible textures for any combination of enables/hovered */ public void setTextures(final WidgetSprites buttonTextures) { this.textures = buttonTextures; + this.resolvedTextures = null; + + requireNonNull(textures.enabled(), "Missing enabled texture"); } /** @@ -155,7 +157,7 @@ private boolean replacedVanillaButton(final Identifier loc) /** * Set the default image. * - * @param loc ResourceLocation for the image. + * @param loc Identifier for the image. */ public void setImage(final Identifier loc) { @@ -173,7 +175,7 @@ public void setImage(final Identifier loc) /** * Set the hover image. * - * @param loc ResourceLocation for the image. + * @param loc Identifier for the image. */ public void setImageHighlight(final Identifier loc) { @@ -186,7 +188,7 @@ public void setImageHighlight(final Identifier loc) /** * Set the disabled image. * - * @param loc ResourceLocation for the image. + * @param loc Identifier for the image. */ public void setImageDisabled(final Identifier loc) { @@ -204,7 +206,7 @@ public void setImageDisabled(final Identifier loc) /** * Set the disabled image. * - * @param loc ResourceLocation for the image. + * @param loc Identifier for the image. */ public void setImageHighlightDisabled(final Identifier loc) { @@ -224,20 +226,21 @@ public void setImageHighlightDisabled(final Identifier loc) @Override public void drawSelf(final BOGuiGraphics target, final double mx, final double my) { - if (!FMLEnvironment.isProduction()) + requireNonNull(textures.enabled(), "Missing enabled texture"); + + if (resolvedTextures == null) { - Objects.requireNonNull(textures.enabled(), () -> id + " | " + window.getXmlResourceLocation()); + resolvedTextures = ResolvedWidgetSprites.fromUnresolved(textures, Image::resolveBlit); } - target.guiGraphics().blitSprite(RenderPipelines.GUI_TEXTURED, textures.get(enabled, wasCursorInPane), x, y, width, height, ARGB.white(1)); + resolvedTextures.getAndPrepare(isEnabled(), wasCursorInPane).blit(target, x, y, width, height); postDrawBackground(target, mx, my); super.drawSelf(target, mx, my); } /** - * Called after drawing the button background. {@link RenderSystem#setShaderColor(float, float, float, float)} might be applied - * according to rendering of the actuall button background. + * Called after drawing the button background. */ public void postDrawBackground(final BOGuiGraphics target, final double mx, final double my) { @@ -283,4 +286,4 @@ public void setTextRenderBox(final int textWidth, final int textHeight) this.textHeight = Mth.clamp(textHeight, 0, height - textOffsetY); recalcTextRendering(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/ldtteam/blockui/controls/CheckBox.java b/src/main/java/com/ldtteam/blockui/controls/CheckBox.java index d3828a31..40142131 100644 --- a/src/main/java/com/ldtteam/blockui/controls/CheckBox.java +++ b/src/main/java/com/ldtteam/blockui/controls/CheckBox.java @@ -3,8 +3,6 @@ import com.ldtteam.blockui.BOGuiGraphics; import com.ldtteam.blockui.PaneParams; import net.minecraft.resources.Identifier; -import net.neoforged.fml.loading.FMLEnvironment; -import java.util.Objects; /** * Checkbox used for toggling a checkmark on and off. @@ -14,7 +12,7 @@ public class CheckBox extends ButtonImage /** * The image for the checkmark to render over the button. */ - protected Identifier checkmarkImage; + protected Identifier checkmarkImage; protected ResolvedBlit resolvedCheckmarkImage; /** @@ -53,11 +51,12 @@ public boolean handleClick(final double mx, final double my) /** * Set the checkmark image. * - * @param loc ResourceLocation for the checkmark. + * @param loc Identifier for the checkmark. */ public void setCheckmarkImage(final Identifier loc) { this.checkmarkImage = loc; + requireNonNull(checkmarkImage, "Missing checkmark texture"); } public Identifier getCheckmarkImage() @@ -68,10 +67,7 @@ public Identifier getCheckmarkImage() @Override public void postDrawBackground(final BOGuiGraphics target, final double mx, final double my) { - if (!FMLEnvironment.isProduction()) - { - Objects.requireNonNull(checkmarkImage, () -> "Missing checkmark source: " + id + " | " + window.getXmlResourceLocation()); - } + requireNonNull(checkmarkImage, "Missing checkmark texture"); if (!checked) { @@ -83,7 +79,7 @@ public void postDrawBackground(final BOGuiGraphics target, final double mx, fina resolvedCheckmarkImage = Image.resolveBlit(checkmarkImage); } - resolvedCheckmarkImage.blit(target.pose(), x, y, width, height); + resolvedCheckmarkImage.blit(target, x, y, width, height); } /** diff --git a/src/main/java/com/ldtteam/blockui/controls/EntityIcon.java b/src/main/java/com/ldtteam/blockui/controls/EntityIcon.java index f45c272b..719f8db9 100644 --- a/src/main/java/com/ldtteam/blockui/controls/EntityIcon.java +++ b/src/main/java/com/ldtteam/blockui/controls/EntityIcon.java @@ -5,90 +5,142 @@ import com.ldtteam.blockui.PaneParams; import com.ldtteam.blockui.controls.AbstractTextBuilder.AutomaticTooltipBuilder; import com.ldtteam.blockui.controls.Tooltip.AutomaticTooltip; -import net.minecraft.client.gui.screens.inventory.InventoryScreen; -import net.minecraft.core.Holder; +import com.ldtteam.common.util.CompoundTagToClassReflection; +import com.mojang.math.Axis; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.renderer.entity.EntityRenderDispatcher; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.state.AvatarRenderState; +import net.minecraft.client.renderer.entity.state.EntityRenderState; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntitySpawnReason; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.phys.AABB; -import org.jetbrains.annotations.NotNull; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityTypes; import org.jetbrains.annotations.Nullable; import org.joml.Matrix3x2fStack; +import org.joml.Quaternionf; +import org.joml.Vector3f; -import java.util.Optional; +import java.util.function.BiConsumer; /** * Control to render an entity as an icon */ -public class EntityIcon extends Pane +public class EntityIcon extends Pane { @Nullable - private LivingEntity entity; - private int count = 1; - private float yaw = 30; - private float pitch = -10; - private float headyaw = 0; + protected STATE entityState; + protected Transformation transformation = new Transformation(); + protected int count = 1; + + private Component tooltipCachedComponent = null; public EntityIcon() { super(); } + @SuppressWarnings("unchecked") public EntityIcon(final PaneParams params) { super(params); + this.count = params.getInteger("count", this.count); + final Identifier entityName = params.getResource("entity"); - if (entityName != null) + if (entityName == null) { - setEntity(entityName); + return; } - this.count = params.getInteger("count", this.count); - this.yaw = params.getFloat("yaw", this.yaw); - this.pitch = params.getFloat("pitch", this.pitch); - this.headyaw = params.getFloat("head", this.headyaw); - } + EntityRenderState ers = new EntityRenderState(); + ers.entityType = BuiltInRegistries.ENTITY_TYPE.get(entityName).get().value(); - public void setEntity(@NotNull Identifier entityId) - { - final Optional>> entityType = BuiltInRegistries.ENTITY_TYPE.get(entityId); - entityType.ifPresentOrElse(e -> setEntity(e.value()), this::resetEntity); - } + if (ers.entityType == EntityTypes.MANNEQUIN || ers.entityType == EntityTypes.PLAYER) + { + // AvatarRenderState is the client-side render-state counterpart for + // both player and mannequin entities. The old port deliberately + // threw here, which made any GUI containing an avatar icon fail + // during construction (and left citizen/character panels empty). + // Use the default skin until a caller supplies a DynamicState with + // a live avatar entity; AvatarRenderState initializes that skin. + ers = new AvatarRenderState(); + ers.entityType = EntityTypes.PLAYER; + } + else + { + // TODO: this doesn't allow player skins + ers = mc.getEntityRenderDispatcher().getRenderer(ers).createRenderState(); + } - public void setEntity(@NotNull EntityType type) - { - final Entity entity = type.create(mc.level, EntitySpawnReason.LOAD); + final CompoundTag ersDataRaw = params.getCompoundTag("renderState"); + if (ersDataRaw != null) + { + CompoundTagToClassReflection.compoundToClassFields(ersDataRaw, ers, "Error parsing renderState (" + getXmlRelatedId() + ")"); + } + setEntityState((STATE) new StaticState(ers)); - if (entity instanceof LivingEntity) + final CompoundTag transformationRaw = params.getCompoundTag("transformation"); + if (transformationRaw != null) { - setEntity((LivingEntity) entity); + transformation.overrideCameraAngle = new Quaternionf(); + CompoundTagToClassReflection.compoundToClassFields(transformationRaw, transformation, "Error parsing ransformation (" + getXmlRelatedId() + ")"); + return; } - else + + // compute some meaningful default + + // TODO: port 26.1 mirality to verify compat with 1.21 code + // missing auto scaling - check if vanilla has something to calc that from ERS + final float yaw = params.getFloat("yaw", 30); + final float pitch = params.getFloat("pitch", -10); + final float headyaw = params.getFloat("head", 0); + + transformation.rotation = Axis.ZP.rotationDegrees(180.0F); + transformation.rotation.mul(Axis.XP.rotationDegrees(pitch)); + + if (ers instanceof final LivingEntityRenderState lers) { - resetEntity(); + lers.yRot = 180.0f + headyaw; + lers.xRot = -pitch; + lers.bodyRot = 180.0f + yaw; + // yHeadRot is not used for lers? } + + transformation.overrideCameraAngle = Axis.XP.rotationDegrees(pitch).conjugate(); + + // poseStack.scale((float) scale, (float) scale, (float) scale); + // final Quaternionf pitchRotation = Axis.XP.rotationDegrees(pitch); + // poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F)); + // poseStack.mulPose(pitchRotation); + // entity.setYRot(180.0F + (float) headYaw); + // entity.setXRot(-pitch); + // if (livingEntity != null) + // { + // livingEntity.yBodyRot = 180.0F + yaw; + // livingEntity.yHeadRot = entity.getYRot(); + // livingEntity.yHeadRotO = entity.getYRot(); + // } + // pitchRotation.conjugate(); + // dispatcher.overrideCameraOrientation(pitchRotation); } - public void setEntity(@NotNull LivingEntity entity) + public void setEntityState(@Nullable final STATE entityState) { - this.entity = entity; - if (onHover instanceof final AutomaticTooltip tooltip) + this.entityState = entityState; + if (entityState == null && this.onHover instanceof AutomaticTooltip) { - tooltip.setText(this.entity.getDisplayName()); + setHoverPane(null); + tooltipCachedComponent = null; } } - public void resetEntity() + public STATE getEntityState() { - this.entity = null; - if (onHover instanceof final AutomaticTooltip tooltip) - { - tooltip.clearText(); - } + return entityState; } public void setCount(final int count) @@ -96,35 +148,129 @@ public void setCount(final int count) this.count = count; } - public void setYaw(final float yaw) + public int getCount() + { + return count; + } + + public void setTransformation(final Transformation transformation) { - this.yaw = yaw; + this.transformation = transformation; } - public void setPitch(final float pitch) + public Transformation getTransformation() { - this.pitch = pitch; + return transformation; } @Override public void drawSelf(final BOGuiGraphics target, final double mx, final double my) { - final Matrix3x2fStack ms = target.guiGraphics().pose(); + final Matrix3x2fStack ms = target.pose(); - if (this.entity != null) + if (this.entityState != null) { - final AABB bb = this.entity.getBoundingBox(); - final int scale = (int) (getHeight() / bb.getYsize() / 1.5); - InventoryScreen.renderEntityInInventoryFollowsMouse(target.guiGraphics(), x, y, x+width, x+height, scale, 2.0f, (float) mx, (float) my, entity); + ms.pushMatrix(); + ms.translate(x, y); + + final EntityRenderState ers = entityState.entityRenderState(mc.getEntityRenderDispatcher()); + + if (ers.nameTag != null) + { + tooltipCachedComponent = ers.nameTag; + ers.nameTag = null; + } + + final ScreenRectangle renderBox = target.calcTransformedPaneBounds(this); + + target.entity(ers, + transformation.scale, + transformation.translation, + transformation.rotation, + transformation.overrideCameraAngle, + renderBox.left(), + renderBox.top(), + renderBox.right(), + renderBox.bottom()); + + if (this.count != 1) + { + final String amount = String.valueOf(count); + ms.translate(getWidth(), getHeight()); + target.text(mc.font, amount, -4 - mc.font.width(amount), -mc.font.lineHeight, -1, true); + } + + ms.popMatrix(); } } @Override public void onUpdate() { - if (this.onHover == null && this.entity != null) + if (this.onHover == null && this.tooltipCachedComponent != null) + { + new AutomaticTooltipBuilder().hoverPane(this).append(tooltipCachedComponent).build(); + } + } + + public interface EntityIconState + { + EntityRenderState entityRenderState(EntityRenderDispatcher entityRenderDispatcher); + } + + public record StaticState(ERS entityRenderState) implements EntityIconState + { + @Override + public EntityRenderState entityRenderState(final EntityRenderDispatcher entityRenderDispatcher) + { + return entityRenderState; + } + } + + public static class DynamicState> implements EntityIconState + { + public static final BiConsumer COMMON_RENDER_STATE_POSTPROCESSING = (entity, ers) -> { + ers.shadowPieces.clear(); + ers.nameTag = entity.getCustomName(); + }; + + private final E entity; + private ERS entityRenderState; + private final BiConsumer entityRenderStateAdjuster; + + public DynamicState(final E entity, @Nullable final BiConsumer entityRenderStateAdjuster) + { + this.entity = entity; + this.entityRenderStateAdjuster = entityRenderStateAdjuster == null ? this::commonRenderStateAdjustments : entityRenderStateAdjuster; + } + + public void commonRenderStateAdjustments(final E entity, final ERS entityRenderState) { - new AutomaticTooltipBuilder().hoverPane(this).build().setText(this.entity.getDisplayName()); + // vanilla + entityRenderState.shadowPieces.clear(); + entityRenderState.outlineColor = 0; + + // ours + entityRenderState.nameTag = entity.getCustomName(); + } + + @SuppressWarnings("unchecked") + @Override + public EntityRenderState entityRenderState(final EntityRenderDispatcher entityRenderDispatcher) + { + final ER renderer = (ER) entityRenderDispatcher.getRenderer(entity); + entityRenderState = renderer.createRenderState(entity, 1.0f); + entityRenderStateAdjuster.accept(entity, entityRenderState); + return entityRenderState; } } + + public class Transformation + { + public float scale = 10.0f; + public Vector3f translation = new Vector3f(); + public Quaternionf rotation = new Quaternionf(); + @Nullable + public Quaternionf overrideCameraAngle = null; + } } diff --git a/src/main/java/com/ldtteam/blockui/controls/Gradient.java b/src/main/java/com/ldtteam/blockui/controls/Gradient.java index a0d71e8e..043c39df 100644 --- a/src/main/java/com/ldtteam/blockui/controls/Gradient.java +++ b/src/main/java/com/ldtteam/blockui/controls/Gradient.java @@ -64,7 +64,7 @@ public void setGradientEnd(final int red, final int green, final int blue, final @Override public void drawSelf(final BOGuiGraphics target, final double mx, final double my) { - target.guiGraphics().fillGradient(x,y,width,height,gradientStart,gradientEnd); + fillGradient(target, x, y, width, height, gradientStart, gradientEnd); super.drawSelf(target, mx, my); } } diff --git a/src/main/java/com/ldtteam/blockui/controls/Image.java b/src/main/java/com/ldtteam/blockui/controls/Image.java index 790f8367..b8476e09 100644 --- a/src/main/java/com/ldtteam/blockui/controls/Image.java +++ b/src/main/java/com/ldtteam/blockui/controls/Image.java @@ -4,35 +4,29 @@ import com.ldtteam.blockui.Pane; import com.ldtteam.blockui.PaneParams; import com.ldtteam.blockui.Parsers; -import com.ldtteam.blockui.mod.Log; +import com.ldtteam.blockui.mod.BlockUI; import com.ldtteam.blockui.util.records.SizeI; -import com.ldtteam.blockui.util.resloc.OutOfJarResourceLocation; import com.ldtteam.blockui.util.texture.OutOfJarTexture; -import com.mojang.blaze3d.platform.NativeImage; -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.metadata.gui.GuiMetadataSection; +import net.minecraft.data.AtlasIds; import net.minecraft.resources.Identifier; -import net.minecraft.util.Tuple; -import net.neoforged.fml.loading.FMLEnvironment; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.NoSuchFileException; -import java.util.Iterator; import java.util.Objects; /** * Simple image element. */ -public class Image extends Pane +public class Image extends Pane { protected Identifier resourceLocation = null; - protected int u = 0; + protected int u = 0; protected int v = 0; protected int uWidth = 0; protected int vHeight = 0; + protected ResolvedBlit resolvedBlit = null; /** * Default Constructor. @@ -61,74 +55,28 @@ public Image(final PaneParams params) vHeight = a.get(1); }); - resourceLocation = params.getResource("source"); + // Images without a source are populated by the owning window at runtime + // (for example Structurize's build-tool rotation indicator). Use the + // vanilla missing sprite until that dynamic value is supplied. + resourceLocation = params.getResource("source", MissingTextureAtlasSprite.getLocation()); } /** - * Load and image from a {@link Identifier} and return a {@link Tuple} containing its width and height. + * Load and image from a {@link Identifier} and return a {@link SizeI} containing its width and height. * * @param resourceLocation The {@link Identifier} pointing to the image. * @return Width and height. */ public static SizeI getImageDimensions(final Identifier resourceLocation) { - // this is called by most of image classes -> parse our textures - OutOfJarTexture.assertLoadedDefaultManagers(resourceLocation); - - final int pos = resourceLocation.getPath().lastIndexOf("."); - - if (pos == -1) - { - try (InputStream is = OutOfJarResourceLocation.openStream(resourceLocation, Minecraft.getInstance().getResourceManager()); - NativeImage nativeImage = NativeImage.read(is)) - { - return new SizeI(nativeImage.getWidth(), nativeImage.getHeight()); - } - catch (final Exception e) - { - throw new IllegalStateException("No extension for file: " + resourceLocation.toString(), e); - } - } - - final String suffix = resourceLocation.getPath().substring(pos + 1); - final Iterator it = ImageIO.getImageReadersBySuffix(suffix); - - while (it.hasNext()) - { - final ImageReader reader = it.next(); - try (InputStream is = OutOfJarResourceLocation.openStream(resourceLocation, Minecraft.getInstance().getResourceManager()); - ImageInputStream stream = ImageIO.createImageInputStream(is)) - { - reader.setInput(stream); - - return new SizeI(reader.getWidth(reader.getMinIndex()), reader.getHeight(reader.getMinIndex())); - } - catch (final NoSuchFileException | FileNotFoundException e) - { - // dont log these, texture manager logs it anyway - } - catch (final IOException e) - { - Log.getLogger().warn(e); - } - finally - { - reader.dispose(); - } - } - - if (!FMLEnvironment.isProduction()) - { - throw new RuntimeException("Couldn't resolve size for image: " + resourceLocation); - } - - return new SizeI(0, 0); + final var texture = Minecraft.getInstance().getTextureManager().getTexture(resourceLocation).getTexture(); + return new SizeI(texture.getWidth(0), texture.getHeight(0)); } /** * Set the image. * - * @param rl ResourceLocation for the image. + * @param rl Identifier for the image. * @param u image x offset. * @param v image y offset. * @param uWidth image width. @@ -140,18 +88,20 @@ public void setImage(final Identifier rl, final int u, final int v, final int uW { return; } + requireNonNull(rl, "Missing image texture"); this.resourceLocation = rl; this.u = u; this.v = v; this.uWidth = uWidth; this.vHeight = vHeight; + this.resolvedBlit = null; } /** * Set the image. * - * @param rl ResourceLocation for the image. + * @param rl Identifier for the image. * @param keepUv whether to keep previous u and v values or use full size */ public void setImage(final Identifier rl, final boolean keepUv) @@ -175,11 +125,71 @@ public void setImage(final Identifier rl, final boolean keepUv) @Override public void drawSelf(final BOGuiGraphics target, final double mx, final double my) { - if (!FMLEnvironment.isProduction()) + requireNonNull(resourceLocation, "Missing image texture"); + + if (resolvedBlit == null) { - Objects.requireNonNull(resourceLocation, () -> "Missing image source: " + id + " | " + window.getXmlResourceLocation()); + resolvedBlit = resolveBlit(resourceLocation, u, v, uWidth, vHeight); } - target.guiGraphics().blit(resourceLocation, x, y, u, v, width, height, uWidth, vHeight); + resolvedBlit.blit(target, x, y, width, height); + } + + /** + * @param resLoc texture resource location + * @return resolved blit - with precomputed values and detached from all possible instances + */ + public static ResolvedBlit resolveBlit(final Identifier resLoc) + { + return resolveBlit(resLoc, 0, 0, 0, 0); + } + + /** + * @param resLoc texture resource location + * @param u in texels + * @param v in texels + * @param uWidth in texels, zero = max + * @param vHeight in texels, zero = max + * @return resolved blit - with precomputed values and detached from all possible instances + */ + public static ResolvedBlit resolveBlit(final Identifier resLoc, final int u, final int v, final int uWidth, final int vHeight) + { + // if bad input skip resolving + if (resLoc == null || resLoc == MissingTextureAtlasSprite.getLocation()) + { + return (ps, x, y, w, h, c) -> blit(ps, MissingTextureAtlasSprite.getLocation(), x, y, w, h, c); + } + + // this is called by most of image classes -> parse our textures + OutOfJarTexture.assertLoadedDefaultManagers(resLoc); + + // Mods may use ordinary textures without registering a custom GUI atlas. + // The old BlockUI atlas manager checked those against the vanilla GUI + // atlas before falling back to a direct texture blit; preserve that + // behavior instead of passing a null atlas id to AtlasManager. + final Identifier atlasId = BlockUI.NAMESPACE_TO_ATLAS_MAP.getOrDefault(resLoc.getNamespace(), AtlasIds.GUI); + final TextureAtlas guiAtlas = Minecraft.getInstance().getAtlasManager().getAtlasOrThrow(atlasId); + final TextureAtlasSprite atlasSprite = guiAtlas.getSprite(resLoc); + + // unless we sprited missing texture pass to sprite blit (intentional object equality) + if (atlasSprite != guiAtlas.missingSprite()) + { + return resolveSprite(atlasSprite, atlasSprite.contents().getAdditionalMetadata(GuiMetadataSection.TYPE).orElse(GuiMetadataSection.DEFAULT).scaling()); + } + + // if full blit do normal blit + if (u == 0 && v == 0 && uWidth == 0 && vHeight == 0) + { + return (ps, x, y, w, h, c) -> blit(ps, resLoc, x, y, w, h, c); + } + + // else map u,v to float + final SizeI mapSize = getImageDimensions(resLoc); + final float uMin = u / (float) mapSize.width(); + final float uMax = uWidth == 0 ? 1.0f : uMin + uWidth / (float) mapSize.width(); + final float vMin = v / (float) mapSize.height(); + final float vMax = vHeight == 0 ? 1.0f : vMin + vHeight / (float) mapSize.height(); + + return (ps, x, y, w, h, c) -> blit(ps, resLoc, x, y, w, h, uMin, vMin, uMax, vMax, c); } } diff --git a/src/main/java/com/ldtteam/blockui/controls/ItemIcon.java b/src/main/java/com/ldtteam/blockui/controls/ItemIcon.java index 39ae37fc..5b2fddc6 100644 --- a/src/main/java/com/ldtteam/blockui/controls/ItemIcon.java +++ b/src/main/java/com/ldtteam/blockui/controls/ItemIcon.java @@ -9,31 +9,35 @@ import com.ldtteam.blockui.mod.item.BlockStateRenderingData; import com.ldtteam.blockui.util.SpacerTextComponent; import com.ldtteam.blockui.util.ToggleableTextComponent; +import com.ldtteam.common.util.BlockToItemHelper; import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen; -import net.minecraft.core.Holder; +import net.minecraft.core.Holder.Reference; +import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.Identifier; -import net.minecraft.world.item.*; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; import net.minecraft.world.item.Item.TooltipContext; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.block.AirBlock; +import net.neoforged.neoforge.client.ClientTooltipFlag; import net.neoforged.neoforge.common.CreativeModeTabRegistry; import org.jetbrains.annotations.Nullable; import org.joml.Matrix3x2fStack; - import java.util.Collections; import java.util.List; -import java.util.Optional; /** * Class of itemIcons in our GUIs. */ public class ItemIcon extends Pane { - protected static final float DEFAULT_ITEMSTACK_SIZE = 16f; + public static final int DEFAULT_ITEMSTACK_SIZE_I = 16; + public static final float DEFAULT_ITEMSTACK_SIZE = 16f; protected static final MutableComponent FIX_VANILLA_TOOLTIP = SpacerTextComponent.of(1); /** @@ -68,10 +72,10 @@ public ItemIcon(final PaneParams params) final Identifier itemName = params.getResource("item"); if (itemName != null) { - final Optional> item = BuiltInRegistries.ITEM.get(itemName); - if (item.isPresent()) + final Item item = BuiltInRegistries.ITEM.get(itemName).map(Reference::value).orElse(null); + if (item != null) { - setItem(item.get().value().getDefaultInstance()); + setItem(item.getDefaultInstance()); } } @@ -126,7 +130,7 @@ public boolean renderItemDecorations() /** * Sets itemStack from blockState. - * + * * @see #setItem(ItemStack) equivalent of setItem(ItemStack) */ public void setItemFromBlockState(final BlockStateRenderingData blockStateExtension) @@ -142,7 +146,7 @@ public void setItemFromBlockState(final BlockStateRenderingData blockStateExtens } if (!itemStack.isEmpty() && blockStateExtension.blockEntity() != null) { - blockStateExtension.blockState().item.blockEntity().saveToItem(itemStack, mc.level.registryAccess()); + BlockToItemHelper.saveBeToItem(blockStateExtension.blockEntity(), itemStack, mc.level.registryAccess()); } onItemUpdate(); } @@ -173,6 +177,13 @@ protected void updateTooltipIfNeeded() if (onHover instanceof final AutomaticTooltip tooltip) { tooltip.setTextOld(getModifiedItemStackTooltip()); + // Clearing an icon intentionally leaves the stack null. The + // old port still dereferenced it while refreshing the + // tooltip, turning an empty/removed inventory slot into a + // client crash. Keep the tooltip metadata empty with no + // stack and restore it when a stack is assigned again. + tooltip.setStyle(itemStack == null ? null : itemStack.get(DataComponents.TOOLTIP_STYLE)); + tooltip.setTooltipComponent(itemStack == null ? null : itemStack.getTooltipImage().orElse(null)); } tooltipUpdateScheduled = false; } @@ -184,15 +195,15 @@ public void drawSelf(final BOGuiGraphics target, final double mx, final double m updateTooltipIfNeeded(); if (!isDataEmpty()) { - final Matrix3x2fStack ms = target.guiGraphics().pose(); + final Matrix3x2fStack ms = target.pose(); ms.pushMatrix(); ms.translate(x, y); ms.scale(this.getWidth() / DEFAULT_ITEMSTACK_SIZE, this.getHeight() / DEFAULT_ITEMSTACK_SIZE); - target.guiGraphics().renderItem(itemStack, 0, 0); + target.item(itemStack, 0, 0); if (renderItemDecorations) { - target.guiGraphics().renderItemDecorations(mc.font, itemStack, 0, 0); + target.renderItemDecorations(itemStack, 0, 0); } ms.popMatrix(); @@ -223,7 +234,7 @@ protected int modifyTooltipName(final List tooltipList, final Tooltip * prevTooltipSize: This value if for determining whether to append "show more info" text or not. * If you add elements which are wrapped via ToggleableTextComponent (and want to show "show more info" text), then add their count to this value. * else if you want to hide the text then set this value to {@code tooltipList.size()} - * + * * @param tooltipList tooltip to modify * @param prevTooltipSize tooltip size before any modifications * @return new prevTooltipSize @@ -236,7 +247,7 @@ protected int appendTooltip(final List tooltipList, final TooltipFlag /** * Adds spacer and optional data * - * INLINE: + * INLINE: * @see CreativeModeInventoryScreen#getTooltipFromContainerItem(ItemStack) */ public List getModifiedItemStackTooltip() @@ -252,7 +263,7 @@ public List getModifiedItemStackTooltip() tooltipFlags = tooltipFlags.asCreative(); } - final List tooltipList = itemStack.getTooltipLines(TooltipContext.of(mc.level), mc.player, tooltipFlags); + final List tooltipList = itemStack.getTooltipLines(TooltipContext.of(mc.level), mc.player, ClientTooltipFlag.of(tooltipFlags)); int nameOffset = 1; nameOffset = modifyTooltipName(tooltipList, tooltipFlags, nameOffset); @@ -294,13 +305,13 @@ public List getModifiedItemStackTooltip() return tooltipList; } - protected static MutableComponent wrapShift(final MutableComponent wrapped) + protected MutableComponent wrapShift(final MutableComponent wrapped) { - return ToggleableTextComponent.of(Minecraft.getInstance()::hasShiftDown, wrapped); + return ToggleableTextComponent.of(mc::hasShiftDown, wrapped); } - protected static MutableComponent wrapShift(final MutableComponent wrapped, final boolean shouldWrap) + protected MutableComponent wrapShift(final MutableComponent wrapped, final boolean shouldWrap) { - return shouldWrap ? ToggleableTextComponent.of(Minecraft.getInstance()::hasShiftDown, wrapped) : wrapped; + return shouldWrap ? ToggleableTextComponent.of(mc::hasShiftDown, wrapped) : wrapped; } } diff --git a/src/main/java/com/ldtteam/blockui/controls/ItemIconWithBlockState.java b/src/main/java/com/ldtteam/blockui/controls/ItemIconWithBlockState.java index dc121b20..9bb0c4bd 100644 --- a/src/main/java/com/ldtteam/blockui/controls/ItemIconWithBlockState.java +++ b/src/main/java/com/ldtteam/blockui/controls/ItemIconWithBlockState.java @@ -4,11 +4,11 @@ import com.ldtteam.blockui.PaneParams; import com.ldtteam.blockui.mod.Log; import com.ldtteam.blockui.mod.item.BlockStateRenderingData; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; +import com.ldtteam.blockui.mod.item.BlockStatePipRenderer.BlockStateRenderState; +import org.joml.Matrix3x2fStack; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.ChatFormatting; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.BuiltInRegistries; @@ -23,9 +23,10 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.component.BlockItemStateProperties; -import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.item.component.TypedEntityData; import net.minecraft.world.level.block.RenderShape; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.Property; @@ -84,7 +85,7 @@ public ItemIconWithBlockState(final PaneParams params) try { newItemStack.applyComponents( - DataComponentMap.CODEC.decode(NbtOps.INSTANCE, TagParser.parseTag(nbt)).getOrThrow().getFirst()); + DataComponentMap.CODEC.decode(NbtOps.INSTANCE, TagParser.parseCompoundFully(nbt)).getOrThrow().getFirst()); } catch (final CommandSyntaxException | IllegalStateException e) { @@ -134,25 +135,25 @@ public void drawSelf(final BOGuiGraphics target, final double mx, final double m super.drawSelf(target, mx, my); return; } - - final PoseStack ms = target.pose(); - ms.pushPose(); - ms.translate(x, y, 0.0f); - ms.scale(this.getWidth() / DEFAULT_ITEMSTACK_SIZE, this.getHeight() / DEFAULT_ITEMSTACK_SIZE, 1.0f); + + final Matrix3x2fStack ms = target.pose(); + ms.pushMatrix(); + ms.translate(x, y); + ms.scale(this.getWidth() / DEFAULT_ITEMSTACK_SIZE, this.getHeight() / DEFAULT_ITEMSTACK_SIZE); if (renderItemAlongBlockState) { - target.renderItem(itemStack, 0, 0); + target.item(itemStack, 0, 0); } - target.renderBlockStateAsItem(blockStateExtension, itemStack); + ms.scale(1.0f / BlockStateRenderState.SCALE_FACTOR, 1.0f / BlockStateRenderState.SCALE_FACTOR); + BlockStateRenderState.submit(target, blockStateExtension, itemStack); + ms.scale(BlockStateRenderState.SCALE_FACTOR, BlockStateRenderState.SCALE_FACTOR); if (renderItemDecorations) { target.renderItemDecorations(itemStack, 0, 0); } - RenderSystem.defaultBlendFunc(); - RenderSystem.disableBlend(); - ms.popPose(); + ms.popMatrix(); } @Override @@ -308,10 +309,13 @@ protected void readBlockStateFromCurrentItemStack() } // try parsing blockentity - final CompoundTag blockEntityTag = itemStack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY).copyTag(); + @Nullable + final TypedEntityData> blockEntityData = itemStack.get(DataComponents.BLOCK_ENTITY_DATA); BlockEntity be = null; - if (!blockEntityTag.isEmpty()) + if (blockEntityData != null) { + final CompoundTag blockEntityTag = blockEntityData.copyTagWithoutId(); + blockEntityTag.store("id", BuiltInRegistries.BLOCK_ENTITY_TYPE.byNameCodec(), blockEntityData.type()); try { // use probably invalid pos diff --git a/src/main/java/com/ldtteam/blockui/controls/ItemIconWithProperties.java b/src/main/java/com/ldtteam/blockui/controls/ItemIconWithProperties.java deleted file mode 100644 index 9822a909..00000000 --- a/src/main/java/com/ldtteam/blockui/controls/ItemIconWithProperties.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.ldtteam.blockui.controls; - -import com.ldtteam.blockui.BOGuiGraphics; -import com.ldtteam.blockui.PaneParams; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import net.minecraft.client.renderer.item.ItemProperties; -import net.minecraft.client.renderer.item.ItemPropertyFunction; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.Tag; -import net.minecraft.nbt.TagParser; -import net.minecraft.resources.Identifier; -import net.minecraft.world.item.Item; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * Useful for overriding things like clock/compass textures. In xml defined through {@value #PARAM_PROPERTIES} key using nbt: - * {:{:, ...}, ...}. - *

- * Special keys: {@value #NBT_CURRENT_ITEM} - refers to xml item (resolved during parsing not dynamic), - * {@value #NBT_GENERIC_KEY} - generic properties - * - * @see ItemProperties - */ -@SuppressWarnings("deprecation") -public class ItemIconWithProperties extends ItemIcon -{ - private static final String NBT_GENERIC_KEY = "_generic"; - private static final String NBT_CURRENT_ITEM = "_item"; - - public static final String PARAM_PROPERTIES = "properties"; - - protected final Map genericPropertyOverrides = new HashMap<>(); - protected final Map> itemPropertyOverrides = new HashMap<>(); - private Map currentItemOverrides = Collections.emptyMap(); - - public ItemIconWithProperties() - { - super(); - } - - public ItemIconWithProperties(final PaneParams paneParams) - { - super(paneParams); - - final String data = paneParams.getString(PARAM_PROPERTIES); - if (data != null && itemStack != null) - { - final CompoundTag tag; - try - { - tag = TagParser.parseTag(data); - } - catch (CommandSyntaxException e) - { - throw new RuntimeException(data, null); - } - tag.getAllKeys().forEach(itemKey -> { - if (tag.contains(itemKey, Tag.TAG_COMPOUND)) - { - final CompoundTag child = tag.getCompound(itemKey); - final var itemOverrides = NBT_GENERIC_KEY.equals(itemKey) ? genericPropertyOverrides : - itemPropertyOverrides.computeIfAbsent(NBT_CURRENT_ITEM.equals(itemKey) ? itemStack.getItem() : - BuiltInRegistries.ITEM.get(Identifier.parse(itemKey)), i -> new HashMap<>()); - - child.getAllKeys().forEach(key -> { - if (child.contains(key, Tag.TAG_ANY_NUMERIC)) - { - final float value = child.getFloat(key); // intentionally ouf of lambda - itemOverrides.put(Identifier.parse(key), (itemStack, level, entity, seee) -> value); - } - }); - } - }); - - onItemUpdate(); - } - } - - /** - * Short call for adding itemProperty to current item - */ - public void addPropertyForCurrentItem(final Identifier propertyKey, final ItemPropertyFunction property) - { - itemPropertyOverrides - .computeIfAbsent(Objects.requireNonNull(itemStack, "Call #setItem before this method").getItem(), item -> new HashMap<>()) - .put(propertyKey, property); - } - - /** - * @return modifiable all item-based overrides - */ - public Map> getItemPropertyOverrides() - { - return itemPropertyOverrides; - } - - /** - * @return modifiable generic overrides - */ - public Map getGenericPropertyOverrides() - { - return genericPropertyOverrides; - } - - @Override - public void drawSelf(final BOGuiGraphics target, final double mx, final double my) - { - if (isDataEmpty()) - { - return; - } - if (currentItemOverrides.isEmpty() && genericPropertyOverrides.isEmpty()) - { - super.drawSelf(target, mx, my); - return; - } - final Item item = itemStack.getItem(); - - // generic - final Map oldGenericVals = - genericPropertyOverrides.isEmpty() ? Collections.emptyMap() : new HashMap<>(); - genericPropertyOverrides.forEach((key, val) -> { - oldGenericVals.put(key, ItemProperties.getProperty(itemStack, key)); - ItemProperties.registerGeneric(key, val); - }); - - // item - final Map oldItemVals = - currentItemOverrides.isEmpty() ? Collections.emptyMap() : new HashMap<>(); - currentItemOverrides.forEach((key, val) -> { - oldItemVals.put(key, ItemProperties.getProperty(itemStack, key)); - ItemProperties.register(item, key, val); - }); - - super.drawSelf(target, mx, my); - - oldItemVals.forEach((key, val) -> ItemProperties.register(item, key, val)); - oldGenericVals.forEach((key, val) -> ItemProperties.registerGeneric(key, val)); - } - - @Override - protected void onItemUpdate() - { - super.onItemUpdate(); - if (itemPropertyOverrides != null) // ctor race condition - { - currentItemOverrides = itemPropertyOverrides.getOrDefault(itemStack.getItem(), Collections.emptyMap()); - } - } -} diff --git a/src/main/java/com/ldtteam/blockui/controls/Scrollbar.java b/src/main/java/com/ldtteam/blockui/controls/Scrollbar.java index 53e5444f..2f8ca842 100644 --- a/src/main/java/com/ldtteam/blockui/controls/Scrollbar.java +++ b/src/main/java/com/ldtteam/blockui/controls/Scrollbar.java @@ -130,16 +130,16 @@ public void drawSelf(final BOGuiGraphics target, final double mx, final double m } // Scroll Area Back - fill(target.pose(), x + offsetX, y + offsetY, width - 2, height, scrollbarBackground); + fill(target, x + offsetX, y + offsetY, width - 2, height, scrollbarBackground); final int renderY = y + (int) getScrollBarYPos(); final int renderHeight = getBarHeight(); // Scroll Bar (Bottom/Right Edge line) - Fill whole Scroll area - fill(target.pose(), x + offsetX, renderY, width - 2, renderHeight, scrollbarColorHighlight); + fill(target, x + offsetX, renderY, width - 2, renderHeight, scrollbarColorHighlight); // Scroll Bar (Inset color) - fill(target.pose(), x + offsetX, renderY, width - 3, renderHeight - 1, scrollbarColor); + fill(target, x + offsetX, renderY, width - 3, renderHeight - 1, scrollbarColor); } @Override @@ -192,7 +192,7 @@ public int getScrollOffsetX() } @Override - public boolean onMouseDrag(final double mx, final double my, final double deltaX, final double deltaY) + public boolean onMouseDrag(final double mx, final double my, final int speed, final double deltaX, final double deltaY) { return true; } diff --git a/src/main/java/com/ldtteam/blockui/controls/TextField.java b/src/main/java/com/ldtteam/blockui/controls/TextField.java index 47086d9b..18e4fb7d 100644 --- a/src/main/java/com/ldtteam/blockui/controls/TextField.java +++ b/src/main/java/com/ldtteam/blockui/controls/TextField.java @@ -5,14 +5,10 @@ import com.ldtteam.blockui.PaneParams; import com.ldtteam.blockui.util.cursor.Cursor; import com.ldtteam.blockui.views.View; -import com.mojang.blaze3d.platform.GlStateManager.LogicOp; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.*; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.input.KeyEvent; import net.minecraft.util.Mth; import org.jetbrains.annotations.Nullable; -import org.joml.Matrix4f; import org.lwjgl.glfw.GLFW; /** @@ -26,8 +22,8 @@ public class TextField extends Pane private static final int DEFAULT_MAX_TEXT_LENGTH = 32; // Attributes protected int maxTextLength = DEFAULT_MAX_TEXT_LENGTH; - protected int textColor = 0xE0E0E0; - protected int textColorDisabled = 0x707070; + protected int textColor = 0xFFE0E0E0; + protected int textColorDisabled = 0xFF707070; protected boolean shadow = true; @Nullable protected String tabNextPaneID = null; @@ -208,25 +204,23 @@ public String getSelectedText() /** * Handle key event. * - * @param c the character. - * @param key the key. * @return if it should be processed or not. */ - private boolean handleKey(final char c, final int key) + private boolean handleKey(final KeyEvent event) { - switch (key) + switch (event.key()) { case GLFW.GLFW_KEY_BACKSPACE: case GLFW.GLFW_KEY_DELETE: - return handleDelete(key); + return handleDelete(event); case GLFW.GLFW_KEY_HOME: case GLFW.GLFW_KEY_END: - return handleHomeEnd(key); + return handleHomeEnd(event); case GLFW.GLFW_KEY_RIGHT: case GLFW.GLFW_KEY_LEFT: - return handleArrowKeys(key); + return handleArrowKeys(event); case GLFW.GLFW_KEY_TAB: return handleTab(); @@ -237,21 +231,15 @@ private boolean handleKey(final char c, final int key) setSelectionEnd(cursorPosition); return true; } - // else fall-through - - default: - return handleChar(c); } + return false; } - private boolean handleChar(final char c) + @Override + public boolean onCharactedEvent(final CharacterEvent event) { - if (filter.isAllowedCharacter(c)) - { - writeText(Character.toString(c)); - return true; - } - return false; + writeText(Character.toString(event.codepoint())); + return true; } private boolean handleTab() @@ -267,14 +255,14 @@ private boolean handleTab() return true; } - private boolean handleArrowKeys(final int key) + private boolean handleArrowKeys(final KeyEvent event) { - final int direction = (key == GLFW.GLFW_KEY_LEFT) ? -1 : 1; + final int direction = (event.key() == GLFW.GLFW_KEY_LEFT) ? -1 : 1; - if (Screen.hasShiftDown()) + if (event.hasShiftDown()) { - if (Screen.hasControlDown()) + if (event.hasControlDownWithQuirk()) { setSelectionEnd(getNthWordFromPos(direction, getSelectionEnd())); } @@ -283,7 +271,7 @@ private boolean handleArrowKeys(final int key) setSelectionEnd(getSelectionEnd() + direction); } } - else if (Screen.hasControlDown()) + else if (event.hasControlDownWithQuirk()) { setCursorPosition(getNthWordFromCursor(direction)); } @@ -301,11 +289,11 @@ else if (Screen.hasControlDown()) return true; } - private boolean handleHomeEnd(final int key) + private boolean handleHomeEnd(final KeyEvent event) { - final int position = (key == GLFW.GLFW_KEY_HOME) ? 0 : text.length(); + final int position = (event.key() == GLFW.GLFW_KEY_HOME) ? 0 : text.length(); - if (Screen.hasShiftDown()) + if (event.hasControlDownWithQuirk()) { setSelectionEnd(position); } @@ -316,11 +304,11 @@ private boolean handleHomeEnd(final int key) return true; } - private boolean handleDelete(final int key) + private boolean handleDelete(final KeyEvent event) { - final int direction = (key == GLFW.GLFW_KEY_BACKSPACE) ? -1 : 1; + final int direction = (event.key() == GLFW.GLFW_KEY_BACKSPACE) ? -1 : 1; - if (Screen.hasControlDown()) + if (event.hasControlDownWithQuirk()) { deleteWords(direction); } @@ -394,7 +382,7 @@ else if (cursorBeforeEnd && shadow) { if (cursorBeforeEnd) { - fill(target.pose(), cursorX, drawY - 1, 1, 1 + mc.font.lineHeight, RECT_COLOR); + fill(target, cursorX, drawY - 1, 1, 1 + mc.font.lineHeight, RECT_COLOR); } else { @@ -420,21 +408,7 @@ else if (cursorBeforeEnd && shadow) selectionEndX = x + width; } - final Matrix4f m = target.pose().last().pose(); - RenderSystem.setShaderColor(0.0F, 0.0F, 1.0F, 1.0F); - RenderSystem.enableColorLogicOp(); - RenderSystem.logicOp(LogicOp.OR_REVERSE); - RenderSystem.setShader(GameRenderer::getPositionShader); - - final BufferBuilder vertexBuffer = Tesselator.getInstance().begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION); - vertexBuffer.addVertex(m, selectionStartX, drawY - 1, 0.0f); - vertexBuffer.addVertex(m, selectionStartX, drawY + 1 + mc.font.lineHeight, 0.0f); - vertexBuffer.addVertex(m, selectionEndX, drawY + 1 + mc.font.lineHeight, 0.0f); - vertexBuffer.addVertex(m, selectionEndX, drawY - 1, 0.0f); - BufferUploader.drawWithShader(vertexBuffer.build()); - - RenderSystem.disableColorLogicOp(); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + target.textHighlight(selectionStartX, drawY - 1, selectionEndX, drawY + 1 + mc.font.lineHeight, true); } } @@ -468,33 +442,33 @@ public boolean handleClick(final double mx, final double my) } @Override - public boolean onKeyTyped(final String c, final int key) + public boolean onKeyEvent(final KeyEvent event) { - if (Screen.isCopy(key)) + if (event.isCopy()) { mc.keyboardHandler.setClipboard(getSelectedText()); return true; } - else if (Screen.isCut(key)) + else if (event.isCut()) { mc.keyboardHandler.setClipboard(getSelectedText()); writeText(""); return true; } - else if (Screen.isSelectAll(key)) + else if (event.isSelectAll()) { setCursorPosition(text.length()); setSelectionEnd(0); return true; } - else if (Screen.isPaste(key)) + else if (event.isPaste()) { writeText(mc.keyboardHandler.getClipboard()); return true; } else { - return handleKey(c, key); + return handleKey(event); } } diff --git a/src/main/java/com/ldtteam/blockui/controls/TextFieldVanilla.java b/src/main/java/com/ldtteam/blockui/controls/TextFieldVanilla.java index 248b57f4..6590cac3 100644 --- a/src/main/java/com/ldtteam/blockui/controls/TextFieldVanilla.java +++ b/src/main/java/com/ldtteam/blockui/controls/TextFieldVanilla.java @@ -79,13 +79,13 @@ public int getInternalWidth() @Override public void drawSelf(final BOGuiGraphics target, final double mx, final double my) { - final Matrix3x2fStack ms = target.guiGraphics().pose(); + final Matrix3x2fStack ms = target.pose(); if (backgroundEnabled) { // Draw box - target.guiGraphics().renderOutline(x - 1, y - 1, width + 2, height + 2, backgroundOuterColor); - target.guiGraphics().fill(x, y, width, height, backgroundInnerColor); + drawLineRect(target, x - 1, y - 1, width + 2, height + 2, backgroundOuterColor); + fill(target, x, y, width, height, backgroundInnerColor); ms.pushMatrix(); ms.translate(BACKGROUND_X_TRANSLATE, (height - BACKGROUND_Y_TRANSLATE_OFFSET) / 2); diff --git a/src/main/java/com/ldtteam/blockui/controls/Tooltip.java b/src/main/java/com/ldtteam/blockui/controls/Tooltip.java index 1b28ce4e..6e831d33 100644 --- a/src/main/java/com/ldtteam/blockui/controls/Tooltip.java +++ b/src/main/java/com/ldtteam/blockui/controls/Tooltip.java @@ -5,7 +5,12 @@ import com.ldtteam.blockui.BOScreen; import com.ldtteam.blockui.PaneBuilders; import com.ldtteam.blockui.PaneParams; +import com.ldtteam.blockui.util.records.SizeI; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil; +import net.minecraft.resources.Identifier; +import net.minecraft.world.inventory.tooltip.TooltipComponent; +import org.jetbrains.annotations.Nullable; import org.joml.Matrix3x2fStack; import java.util.Collections; @@ -19,21 +24,28 @@ public class Tooltip extends AbstractTextElement public static final int DEFAULT_MAX_HEIGHT = AbstractTextElement.SIZE_FOR_UNLIMITED_ELEMENTS; private static final int CURSOR_BOX_SIZE = 12; - private static final int Z_OFFSET = 200; - private static final int BACKGROUND_COLOR = 0xf0100010; // TooltipRenderUtil.BACKGROUND_COLOR; - private static final int BORDER_COLOR_A = 0x505000ff; // TooltipRenderUtil.BORDER_COLOR_TOP - private static final int BORDER_COLOR_B = 0x5028007f; // TooltipRenderUtil.BORDER_COLOR_BOTTOM + private static final int CONTENT_PADDING = TooltipRenderUtil.PADDING; // one direction - public static final int DEFAULT_TEXT_COLOR = 0xffffff; // white + public static final int DEFAULT_TEXT_COLOR = 0xFFffffff; // white protected boolean autoWidth = true; protected boolean autoHeight = true; protected int maxWidth = DEFAULT_MAX_WIDTH; protected int maxHeight = DEFAULT_MAX_HEIGHT; + // TODO: make AbstractTextElement accept this as part of text - requires big type changes + @Nullable + protected ClientTooltipComponent tooltipComponent = null; + private SizeI tooltipComponentSize = null; + + @Nullable + protected Identifier style = null; + private ResolvedBlit backgroundBlit = null; + private ResolvedBlit frameBlit = null; + /** * Standard constructor which instantiates the tooltip. - * + * * @see PaneBuilders#tooltipBuilder() * @deprecated {@link PaneBuilders#tooltipBuilder()} */ @@ -61,8 +73,8 @@ public Tooltip(final PaneParams params) protected void init() { textLinespace = 1; - textOffsetX = 4; - textOffsetY = 4; + textOffsetX = CONTENT_PADDING; + textOffsetY = CONTENT_PADDING; hide(); recalcTextRendering(); } @@ -80,11 +92,11 @@ protected void recalcTextRendering() if (autoWidth) { // +1 for shadow - textWidth = maxWidth - 8 + 1; + textWidth = maxWidth - 2 * CONTENT_PADDING + 1; } if (autoHeight) { - textHeight = maxHeight - 8; + textHeight = maxHeight - 2 * CONTENT_PADDING; } super.recalcTextRendering(); @@ -105,6 +117,34 @@ public void setSize(int w, int h) super.setSize(w, h); } + public void setStyle(@Nullable final Identifier style) + { + this.style = style; + backgroundBlit = null; + frameBlit = null; + } + + public Identifier getStyle() + { + return style; + } + + public void setTooltipComponent(@Nullable final TooltipComponent tooltipComponent) + { + setTooltipComponent(tooltipComponent == null ? null : ClientTooltipComponent.create(tooltipComponent)); + } + + public void setTooltipComponent(@Nullable final ClientTooltipComponent tooltipComponent) + { + this.tooltipComponent = tooltipComponent; + tooltipComponentSize = null; + } + + public ClientTooltipComponent getTooltipComponent() + { + return tooltipComponent; + } + @Override public void drawSelf(final BOGuiGraphics ms, final double mx, final double my) { @@ -114,18 +154,33 @@ public void drawSelf(final BOGuiGraphics ms, final double mx, final double my) @Override public void drawSelfLast(final BOGuiGraphics target, final double mx, final double my) { - final Matrix3x2fStack ms = target.guiGraphics().pose(); + final Matrix3x2fStack ms = target.pose(); if (!preparedText.isEmpty() && isEnabled()) { + if (backgroundBlit == null) + { + backgroundBlit = Image.resolveBlit(TooltipRenderUtil.getBackgroundSprite(style), 0, 0, 0, 0); + } + if (frameBlit == null) + { + frameBlit = Image.resolveBlit(TooltipRenderUtil.getFrameSprite(style), 0, 0, 0, 0); + } + if (tooltipComponentSize == null) + { + // -2 for manual adjustment of terrible mojang math + tooltipComponentSize = tooltipComponent == null ? new SizeI(0, 0) : + new SizeI(tooltipComponent.getWidth(mc.font), tooltipComponent.getHeight(mc.font) - 2); + } + recalcPreparedTextBox(); if (autoWidth) { - width = renderedTextWidth + 8; + width = Math.max(renderedTextWidth, tooltipComponentSize.width()) + 2 * CONTENT_PADDING; } if (autoHeight) { - height = renderedTextHeight + 8; + height = renderedTextHeight + tooltipComponentSize.height() + 2 * CONTENT_PADDING; } final BOScreen scr = window.getScreen(); @@ -154,9 +209,28 @@ public void drawSelfLast(final BOGuiGraphics target, final double mx, final doub } // modified INLINE: vanilla Screen#renderTooltip(MatrixStack, List, int, int, FontRenderer) - TooltipRenderUtil.renderTooltipBackground(target.guiGraphics(), x, y, width, height, null); + // INLINE: update from net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil + ms.pushMatrix(); + + final int shift = TooltipRenderUtil.MARGIN; + backgroundBlit.blit(target, x - shift, y - shift, width + 2 * shift, height + 2 * shift); + frameBlit.blit(target, x - shift, y - shift, width + 2 * shift, height + 2 * shift); super.innerDrawSelf(target, mx, my); + + if (tooltipComponent != null) + { + final int adjustedY = y + CONTENT_PADDING + renderedTextHeight + textLinespace; + tooltipComponent.extractText(target, mc.font, x + CONTENT_PADDING, adjustedY); + tooltipComponent.extractImage(mc.font, + x + CONTENT_PADDING, + adjustedY, + Math.min(tooltipComponentSize.width(), width - 2 * CONTENT_PADDING), + Math.min(tooltipComponentSize.height(), height - 2 * CONTENT_PADDING), + target); + } + + ms.popMatrix(); } } diff --git a/src/main/java/com/ldtteam/blockui/hooks/HookManager.java b/src/main/java/com/ldtteam/blockui/hooks/HookManager.java index e445d4c6..98c766b3 100644 --- a/src/main/java/com/ldtteam/blockui/hooks/HookManager.java +++ b/src/main/java/com/ldtteam/blockui/hooks/HookManager.java @@ -1,11 +1,10 @@ package com.ldtteam.blockui.hooks; import com.ldtteam.blockui.mod.Log; -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.Minecraft; +import org.joml.Matrix4fStack; +import net.minecraft.client.renderer.state.level.LevelRenderState; import net.minecraft.core.Registry; import net.minecraft.resources.Identifier; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -17,7 +16,7 @@ /** * Core class for managing and handling gui hooks - * + * * @param instance of U * @param forge-register type * @param hashable thing to hash T, can be same as T @@ -158,7 +157,7 @@ public boolean unregister(final U thing, final TriggerMechanism triggerType) * @param thing instance of registered type * @param partialTicks partialTicks, see world rendering */ - protected abstract void translateToGuiBottomCenter(final PoseStack ms, final T thing, final float partialTicks); + protected abstract void translateToGuiBottomCenter(final Matrix4fStack ms, final T thing, final float partialTicks); protected void tick(final long ticks) { @@ -182,7 +181,7 @@ protected void tick(final long ticks) final WindowEntry window = new WindowEntry(now, thing, hook, HookWindow::new); activeWindows.put(key, window); - window.screen.init(Minecraft.getInstance(), window.screen.getWindow().getWidth(), window.screen.getWindow().getHeight()); + window.screen.init(window.screen.getWindow().getWidth(), window.screen.getWindow().getHeight()); } // already existing entry else if (entry != null) @@ -209,15 +208,15 @@ else if (entry != null) }); } - protected void render(final PoseStack ms, final float partialTicks) + protected void render(final Matrix4fStack ms, final float partialTicks, final LevelRenderState levelRenderState) { activeWindows.values().forEach(entry -> { - ms.pushPose(); + ms.pushMatrix(); translateToGuiBottomCenter(ms, entry.thing, partialTicks); - ms.mulPose(Minecraft.getInstance().getEntityRenderDispatcher().cameraOrientation()); + ms.rotate(levelRenderState.cameraRenderState.orientation); ms.scale(-0.01F, -0.01F, 0.01F); entry.screen.render(ms); - ms.popPose(); + ms.popMatrix(); }); } @@ -251,9 +250,9 @@ public static void setScrollListener(final HookScreen scrollListener) */ protected class HookEntry { - protected final U targetThing; + protected final U targetThing; protected final Identifier guiLoc; - protected final long expirationTime; + protected final long expirationTime; protected final TriggerMechanism trigger; protected final BiPredicate shouldOpen; protected final IGuiActionCallback onOpen; diff --git a/src/main/java/com/ldtteam/blockui/hooks/HookRegistries.java b/src/main/java/com/ldtteam/blockui/hooks/HookRegistries.java index e982db30..6d1292af 100644 --- a/src/main/java/com/ldtteam/blockui/hooks/HookRegistries.java +++ b/src/main/java/com/ldtteam/blockui/hooks/HookRegistries.java @@ -3,8 +3,9 @@ import com.google.common.base.Predicates; import com.ldtteam.blockui.hooks.TriggerMechanism.RangeTriggerMechanism; import com.ldtteam.blockui.hooks.TriggerMechanism.RayTraceTriggerMechanism; -import com.mojang.blaze3d.vertex.PoseStack; +import org.joml.Matrix4fStack; import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.state.level.LevelRenderState; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; @@ -50,11 +51,11 @@ public static void tick(final long ticks) } } - public static void render(final PoseStack matrixStack, final float partialTicks) + public static void render(final Matrix4fStack matrixStack, final float partialTicks, final LevelRenderState levelRenderState) { for (int i = 0; i < REGISTRIES.length; i++) { - REGISTRIES[i].render(matrixStack, partialTicks); + REGISTRIES[i].render(matrixStack, partialTicks, levelRenderState); } } @@ -168,7 +169,7 @@ protected List findTriggered(final EntityType entityType, f mc.player.getBoundingBox().inflate(range.getSearchRange()), Predicates.alwaysTrue()); - case RayTraceTriggerMechanism rayTrace -> { + case RayTraceTriggerMechanism _ -> { if (mc.hitResult != null && mc.hitResult instanceof EntityHitResult entityHitResult) { final Entity entity = entityHitResult.getEntity(); @@ -192,12 +193,12 @@ protected Entity keyMapper(final Entity thing) } @Override - protected void translateToGuiBottomCenter(final PoseStack ms, final Entity entity, final float partialTicks) + protected void translateToGuiBottomCenter(final Matrix4fStack ms, final Entity entity, final float partialTicks) { - final double x = Mth.lerp(partialTicks, entity.xOld, entity.getX()); - final double y = Mth.lerp(partialTicks, entity.yOld, entity.getY()); - final double z = Mth.lerp(partialTicks, entity.zOld, entity.getZ()); - ms.translate(x, y + entity.getBbHeight() + 0.3d, z); + final float x = (float) Mth.lerp(partialTicks, entity.xOld, entity.getX()); + final float y = (float) Mth.lerp(partialTicks, entity.yOld, entity.getY()); + final float z = (float) Mth.lerp(partialTicks, entity.zOld, entity.getZ()); + ms.translate(x, y + entity.getBbHeight() + 0.3f, z); } } @@ -342,7 +343,7 @@ protected List findTriggered(final BlockEntityType teType, final yield targets; } - case RayTraceTriggerMechanism rayTrace -> { + case RayTraceTriggerMechanism _ -> { if (mc.hitResult != null && mc.hitResult instanceof BlockHitResult blockHitResult) { final BlockEntity te = mc.level.getBlockEntity(blockHitResult.getBlockPos()); @@ -366,9 +367,9 @@ protected BlockPos keyMapper(final BlockEntity thing) } @Override - protected void translateToGuiBottomCenter(final PoseStack ms, final BlockEntity thing, final float partialTicks) + protected void translateToGuiBottomCenter(final Matrix4fStack ms, final BlockEntity thing, final float partialTicks) { - ms.translate(thing.getBlockPos().getX() + 0.5d, thing.getBlockPos().getY() + 1.1d, thing.getBlockPos().getZ() + 0.5d); + ms.translate(thing.getBlockPos().getX() + 0.5f, thing.getBlockPos().getY() + 1.1f, thing.getBlockPos().getZ() + 0.5f); } } } diff --git a/src/main/java/com/ldtteam/blockui/hooks/HookScreen.java b/src/main/java/com/ldtteam/blockui/hooks/HookScreen.java index 60a096cc..dc3bdf31 100644 --- a/src/main/java/com/ldtteam/blockui/hooks/HookScreen.java +++ b/src/main/java/com/ldtteam/blockui/hooks/HookScreen.java @@ -3,12 +3,11 @@ import com.ldtteam.blockui.BOScreen; import com.ldtteam.blockui.hooks.TriggerMechanism.RayTraceTriggerMechanism; import com.ldtteam.blockui.views.ScrollingList; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.ReportedException; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import org.joml.Matrix4fStack; /** * Screen wrapper. @@ -28,23 +27,24 @@ public class HookScreen extends BOScreen @Override @Deprecated - public void render(final GuiGraphics target, final int mx, final int my, final float f) + public void extractRenderState(final GuiGraphicsExtractor target, final int mx, final int my, final float f) { - render(target.pose()); + render(null); } - public void render(final PoseStack ms) + public void render(final Matrix4fStack ms) { - if (minecraft == null || !isOpen) // should never happen though + if (minecraft == null || ms == null || !isOpen) // should never happen though { return; } - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - RenderSystem.enableDepthTest(); - RenderSystem.disableBlend(); - RenderSystem.defaultBlendFunc(); - ms.translate(-width / 2, -height, 0.0d); + // TODO: rework in-game gui rendering + // RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + // RenderSystem.enableDepthTest(); + // RenderSystem.disableBlend(); + // RenderSystem.defaultBlendFunc(); + ms.translate(-width / 2, -height, 0.0f); try { throw new UnsupportedOperationException("need port fix"); diff --git a/src/main/java/com/ldtteam/blockui/hooks/HookWindow.java b/src/main/java/com/ldtteam/blockui/hooks/HookWindow.java index 2386964a..168518ea 100644 --- a/src/main/java/com/ldtteam/blockui/hooks/HookWindow.java +++ b/src/main/java/com/ldtteam/blockui/hooks/HookWindow.java @@ -1,6 +1,5 @@ package com.ldtteam.blockui.hooks; -import com.ldtteam.blockui.Loader; import com.ldtteam.blockui.views.BOWindow; import net.minecraft.resources.Identifier; @@ -14,8 +13,7 @@ public class HookWindow extends BOWindow HookWindow(final HookManager.WindowEntry windowHolder) { - super(); - Loader.createFromXMLFile(windowHolder.hook.guiLoc, this); + super(windowHolder.hook.guiLoc, true); this.windowHolder = windowHolder; screen = new HookScreen(this); diff --git a/src/main/java/com/ldtteam/blockui/mod/BlockUI.java b/src/main/java/com/ldtteam/blockui/mod/BlockUI.java index 193a411d..c7720383 100644 --- a/src/main/java/com/ldtteam/blockui/mod/BlockUI.java +++ b/src/main/java/com/ldtteam/blockui/mod/BlockUI.java @@ -1,16 +1,24 @@ package com.ldtteam.blockui.mod; +import net.minecraft.resources.Identifier; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; import net.neoforged.fml.javafmlmod.FMLModContainer; import net.neoforged.neoforge.common.NeoForge; +import java.util.HashMap; +import java.util.Map; @Mod(BlockUI.MOD_ID) public class BlockUI { public static final String MOD_ID = "blockui"; + /** + * If your mod is using GUI atlas register it here so we know it exists. + */ + public static final Map NAMESPACE_TO_ATLAS_MAP = new HashMap<>(); + public BlockUI(final FMLModContainer modContainer, final Dist dist) { final IEventBus modBus = modContainer.getEventBus(); @@ -22,4 +30,9 @@ public BlockUI(final FMLModContainer modContainer, final Dist dist) forgeBus.register(ClientEventSubscriber.class); } } + + public static Identifier resLoc(final String path) + { + return Identifier.fromNamespaceAndPath(MOD_ID, path); + } } diff --git a/src/main/java/com/ldtteam/blockui/mod/ClientEventSubscriber.java b/src/main/java/com/ldtteam/blockui/mod/ClientEventSubscriber.java index a0759a59..0a155725 100644 --- a/src/main/java/com/ldtteam/blockui/mod/ClientEventSubscriber.java +++ b/src/main/java/com/ldtteam/blockui/mod/ClientEventSubscriber.java @@ -1,6 +1,5 @@ package com.ldtteam.blockui.mod; -import com.ldtteam.blockui.AtlasManager; import com.ldtteam.blockui.BOScreen; import com.ldtteam.blockui.PaneBuilders; import com.ldtteam.blockui.controls.Button; @@ -10,15 +9,16 @@ import com.ldtteam.blockui.hooks.HookManager; import com.ldtteam.blockui.hooks.HookRegistries; import com.ldtteam.blockui.mod.container.ContainerHook; +import com.ldtteam.blockui.util.SpacerTextComponent; import com.ldtteam.blockui.util.resloc.OutOfJarResourceLocation; import com.ldtteam.blockui.views.BOWindow; import com.mojang.blaze3d.platform.InputConstants; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.resources.PlayerSkin; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; import net.minecraft.resources.Identifier; +import net.minecraft.util.profiling.Profiler; +import net.minecraft.world.entity.player.PlayerSkin; import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.ModList; @@ -29,6 +29,8 @@ import net.neoforged.neoforge.event.TagsUpdatedEvent; import org.lwjgl.glfw.GLFW; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Consumer; @@ -42,13 +44,13 @@ public class ClientEventSubscriber /* TODO: fixme public static void renderWorldLastEvent(@NotNull final RenderLevelLastEvent event) { - final PoseStack ps = event.getPoseStack(); + final Matrix3x2fStack ps = event.getPoseStack(); final Vec3 viewPosition = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition(); - ps.pushPose(); + ps.pushMatrix(); ps.translate(-viewPosition.x(), -viewPosition.y(), -viewPosition.z()); HookRegistries.render(ps, event.getPartialTick()); - ps.popPose(); + ps.popMatrix(); }*/ /** @@ -60,43 +62,81 @@ public static void renderWorldLastEvent(@NotNull final RenderLevelLastEvent even @SubscribeEvent public static void onClientTickStart(final ClientTickEvent.Pre event) { - if (Screen.hasAltDown() && Screen.hasControlDown() && Screen.hasShiftDown()) + if (Minecraft.getInstance().hasAltDown() && Minecraft.getInstance().hasControlDown() && Minecraft.getInstance().hasShiftDown()) { - if (InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), GLFW.GLFW_KEY_X)) + if (InputConstants.isKeyDown(Minecraft.getInstance().getWindow(), GLFW.GLFW_KEY_X) && + !(Minecraft.getInstance().gui.screen() instanceof final BOScreen screen && + screen.getWindow().getXmlResourceLocation().getPath().equals("test_gui"))) { - final BOWindow window = new BOWindow(); + final BOWindow window = new BOWindow(BlockUI.resLoc("test_gui"), false) + { + @Override + public void onUpdate() + { + this.blurBackground = Minecraft.getInstance().hasControlDown(); + this.lightbox = Minecraft.getInstance().hasShiftDown(); + super.onUpdate(); + } + }; int id = 0; - final Button dumpAtlases = createTestGuiButton(id++, "Dump mod atlases to run folder", null); + final Button dumpAtlases = createTestGuiButton(id++, "Dump ALL atlases to run folder", null); dumpAtlases.setHandler(b -> { final Path dumpingFolder = Path.of("atlas_dump").toAbsolutePath().normalize(); - Minecraft.getInstance().player.sendSystemMessage(Component.literal("Dumping atlases into: " + dumpingFolder.toString())); - AtlasManager.INSTANCE.dumpAtlases(dumpingFolder); + Minecraft.getInstance().player + .sendSystemMessage(Component.literal("Dumping atlases into: " + dumpingFolder.toString())); + Minecraft.getInstance().getAtlasManager().forEach((resLoc, atlas) -> { + try + { + Files.createDirectories(dumpingFolder); + atlas.dumpContents(resLoc, dumpingFolder); + } + catch (IOException e) + { + e.printStackTrace(); + } + }); }); window.addChild(dumpAtlases); - window.addChild(createTestGuiButton(id++, "General All-in-one", Identifier.fromNamespaceAndPath(BlockUI.MOD_ID, "gui/test.xml"), parent -> { - parent.findPaneOfTypeByID("missing_out_of_jar", Image.class).setImage(OutOfJarResourceLocation.ofMinecraftFolder(BlockUI.MOD_ID, "missing_out_of_jar.png"), false); - parent.findPaneOfTypeByID("working_out_of_jar", Image.class).setImage(OutOfJarResourceLocation.of(BlockUI.MOD_ID, Path.of("../../src/test/resources/button.png")), false); + window.addChild(createTestGuiButton(id++, "General All-in-one", BlockUI.resLoc("gui/test.xml"), parent -> { + parent.findPaneOfTypeByID("missing_out_of_jar", Image.class) + .setImage(OutOfJarResourceLocation.ofMinecraftFolder(BlockUI.MOD_ID, "missing_out_of_jar.png"), false); + parent.findPaneOfTypeByID("working_out_of_jar", Image.class) + .setImage(OutOfJarResourceLocation.of(BlockUI.MOD_ID, Path.of("../../src/test/resources/button.png")), false); OutOfJarResourceLocation.ofMinecraftSkin(Minecraft.getInstance(), Minecraft.getInstance().getGameProfile(), null) .thenAccept(resLoc -> parent.findPaneOfTypeByID("player_skin", Image.class).setImage(resLoc, false)); - OutOfJarResourceLocation.ofMinecraftSkin(Minecraft.getInstance(), Minecraft.getInstance().getGameProfile(), PlayerSkin::capeTexture) - .thenAccept(resLoc -> {if (resLoc!=null){parent.findPaneOfTypeByID("player_cape", Image.class).setImage(resLoc, false);}}); - OutOfJarResourceLocation.ofMinecraftSkin(Minecraft.getInstance(), Minecraft.getInstance().getGameProfile(), PlayerSkin::elytraTexture) - .thenAccept(resLoc -> {if (resLoc!=null){parent.findPaneOfTypeByID("player_elytra", Image.class).setImage(resLoc, false);}}); + OutOfJarResourceLocation + .ofMinecraftSkin(Minecraft.getInstance(), Minecraft.getInstance().getGameProfile(), PlayerSkin::cape) + .thenAccept(resLoc -> { + if (resLoc != null) + { + parent.findPaneOfTypeByID("player_cape", Image.class).setImage(resLoc, false); + } + }); + OutOfJarResourceLocation + .ofMinecraftSkin(Minecraft.getInstance(), Minecraft.getInstance().getGameProfile(), PlayerSkin::elytra) + .thenAccept(resLoc -> { + if (resLoc != null) + { + parent.findPaneOfTypeByID("player_elytra", Image.class).setImage(resLoc, false); + } + }); })); - window.addChild(createTestGuiButton(id++, "Tooltip Positioning", Identifier.fromNamespaceAndPath(BlockUI.MOD_ID, "gui/test2.xml"))); - window.addChild(createTestGuiButton(id++, "ItemIcon To BlockState", Identifier.fromNamespaceAndPath(BlockUI.MOD_ID, "gui/test3.xml"), BlockStateTestGui::setup)); - window.addChild(createTestGuiButton(id++, "Scrolling Lists", Identifier.fromNamespaceAndPath(BlockUI.MOD_ID, "gui/test4.xml"), ScrollingListsGui::setup)); + window.addChild(createTestGuiButton(id++, "Tooltip Positioning", BlockUI.resLoc("gui/test2.xml"))); + window.addChild(createTestGuiButton(id++, "ItemIcon To BlockState", BlockUI.resLoc("gui/test3.xml"), BlockStateTestGui::setup)); + window.addChild(createTestGuiButton(id++, "Scrolling Lists", BlockUI.resLoc("gui/test4.xml"), ScrollingListsGui::setup)); final Text builderTest = new Text(); - builderTest.setSize(ButtonImage.DEFAULT_BUTTON_WIDTH * 2 + 20, ButtonImage.DEFAULT_BUTTON_HEIGHT); + builderTest.setSize(ButtonImage.DEFAULT_BUTTON_WIDTH * 2 + 20, ButtonImage.DEFAULT_BUTTON_HEIGHT * 2); builderTest.setPosition(0, ((id + 1) / 2) * (builderTest.getHeight() + 10)); PaneBuilders.textBuilder() .append(Component.literal(BlockUI.MOD_ID)) .append(Component.literal(" - ")) .append(Component.literal(ModList.get().getModFileById(BlockUI.MOD_ID).versionString())) .paragraphBreak() + .append(SpacerTextComponent.of(5)) + .newLine() .colorName("red") .underlined() .append(Component.translatable("blockui.tooltip.item_additional_info", @@ -119,9 +159,9 @@ public static void onClientTickEnd(final ClientTickEvent.Post event) { if (Minecraft.getInstance().level != null) { - Minecraft.getInstance().getProfiler().push("hook_manager_tick"); + Profiler.get().push("hook_manager_tick"); HookRegistries.tick(Minecraft.getInstance().level.getGameTime()); - Minecraft.getInstance().getProfiler().pop(); + Profiler.get().pop(); } } @@ -175,7 +215,7 @@ public static void onTagsUpdated(final TagsUpdatedEvent event) @SubscribeEvent public static void renderOverlay(final RenderGuiLayerEvent.Pre event) { - if (Minecraft.getInstance().screen instanceof BOScreen && event.getName().equals(VanillaGuiLayers.CROSSHAIR)) + if (Minecraft.getInstance().gui.screen() instanceof BOScreen && event.getName().equals(VanillaGuiLayers.CROSSHAIR)) { event.setCanceled(true); } diff --git a/src/main/java/com/ldtteam/blockui/mod/ClientLifecycleSubscriber.java b/src/main/java/com/ldtteam/blockui/mod/ClientLifecycleSubscriber.java index bf2fcbad..1e1bb019 100644 --- a/src/main/java/com/ldtteam/blockui/mod/ClientLifecycleSubscriber.java +++ b/src/main/java/com/ldtteam/blockui/mod/ClientLifecycleSubscriber.java @@ -1,30 +1,40 @@ package com.ldtteam.blockui.mod; -import com.ldtteam.blockui.AtlasManager; import com.ldtteam.blockui.Loader; -import net.minecraft.client.renderer.BiomeColors; -import net.minecraft.world.level.block.Blocks; +import com.ldtteam.blockui.UiRenderMacros; +import com.ldtteam.blockui.mod.item.BlockStatePipRenderer; +import com.ldtteam.blockui.mod.item.BlockStatePipRenderer.BlockStateRenderState; +import net.minecraft.client.resources.metadata.gui.GuiMetadataSection; +import net.minecraft.client.resources.model.sprite.AtlasManager.AtlasConfig; +import net.minecraft.data.AtlasIds; +import net.minecraft.resources.Identifier; import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; -import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; +import net.neoforged.neoforge.client.event.AddClientReloadListenersEvent; +import net.neoforged.neoforge.client.event.RegisterPictureInPictureRenderersEvent; +import net.neoforged.neoforge.client.event.RegisterRenderPipelinesEvent; +import net.neoforged.neoforge.client.event.RegisterTextureAtlasesEvent; import net.neoforged.neoforge.event.ModMismatchEvent; +import java.util.Set; + public class ClientLifecycleSubscriber { @SubscribeEvent - public static void onRegisterReloadListeners(final RegisterClientReloadListenersEvent event) + public static void onAddClientReloadListeners(final AddClientReloadListenersEvent event) { - event.registerReloadListener(Loader.INSTANCE); - AtlasManager.INSTANCE.addAtlas(event::registerReloadListener, BlockUI.MOD_ID); + event.addListener(Loader.RELOADABLE_LISTEN_RES_LOC, Loader.INSTANCE); } @SubscribeEvent - public static void onRegisterBlockColor(final RegisterColorHandlersEvent.Block event) + public static void onRegisterTextureAtlases(final RegisterTextureAtlasesEvent event) { - // replace cauldron with plains default color (4159204, with slighty more light in HSL += 8%) - event.register( - (state, level, pos, tintIndex) -> level != null && pos != null ? BiomeColors.getAverageWaterColor(level, pos) : 0x638fe9, - Blocks.WATER_CAULDRON); + // register vanilla + BlockUI.NAMESPACE_TO_ATLAS_MAP.put(Identifier.DEFAULT_NAMESPACE, AtlasIds.GUI); + + // register us + final Identifier atlasKey = BlockUI.resLoc("blockui_gui"); + BlockUI.NAMESPACE_TO_ATLAS_MAP.put(BlockUI.MOD_ID, atlasKey); + event.register(new AtlasConfig(BlockUI.resLoc("textures/atlas/blockui_gui.png"), atlasKey, false, Set.of(GuiMetadataSection.TYPE))); } @SubscribeEvent @@ -33,4 +43,19 @@ public static void onModMismatch(final ModMismatchEvent event) // there are no world data and rest is mod compat anyway event.getVersionDifference(BlockUI.MOD_ID).ifPresent(id -> event.markResolved(BlockUI.MOD_ID)); } + + @SubscribeEvent + public static void onRegisterRenderPipelines(final RegisterRenderPipelinesEvent event) + { + event.registerPipeline(UiRenderMacros.GUI_POS_COLOR_LINES); + event.registerPipeline(UiRenderMacros.GUI_POS_COLOR_TRIANGLES); + event.registerPipeline(UiRenderMacros.GUI_POS_TEX_COLOR_TRIANGLES); + event.registerPipeline(UiRenderMacros.GUI_POS_TEX_TRIANGLES); + } + + @SubscribeEvent + public static void onRegisterPictureInPictureRenderers(final RegisterPictureInPictureRenderersEvent event) + { + event.register(BlockStateRenderState.class, BlockStatePipRenderer::new); + } } diff --git a/src/main/java/com/ldtteam/blockui/mod/container/ContainerHook.java b/src/main/java/com/ldtteam/blockui/mod/container/ContainerHook.java index d8cc4455..2b6f3ec5 100644 --- a/src/main/java/com/ldtteam/blockui/mod/container/ContainerHook.java +++ b/src/main/java/com/ldtteam/blockui/mod/container/ContainerHook.java @@ -23,6 +23,7 @@ import net.minecraft.world.Container; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTypes; import net.minecraft.world.level.block.entity.BlockEntityType; import java.util.ArrayList; import java.util.List; @@ -30,7 +31,7 @@ public class ContainerHook { - public static TagKey> CONTAINER_TAG = TagKey.create(Registries.BLOCK_ENTITY_TYPE, Identifier.fromNamespaceAndPath(BlockUI.MOD_ID, "container_gui")); + public static TagKey> CONTAINER_TAG = TagKey.create(Registries.BLOCK_ENTITY_TYPE, BlockUI.resLoc("container_gui")); public static void init() { @@ -39,7 +40,7 @@ public static void init() return; } - final Identifier gui_loc = Identifier.fromNamespaceAndPath(BlockUI.MOD_ID, "gui/container.xml"); + final Identifier gui_loc = BlockUI.resLoc("gui/container.xml"); // TODO: properly support tag reloading for (final Holder> beType : BuiltInRegistries.BLOCK_ENTITY_TYPE.getTagOrEmpty(CONTAINER_TAG)) { @@ -69,8 +70,8 @@ public static void onContainerGuiOpen(final BlockEntity thing, final BOWindow wi .getBlockEntity(thing.getBlockPos()); final Container container = worldBlockEntity instanceof Container c ? c : - (worldBlockEntity.getType() == BlockEntityType.ENDER_CHEST ? integratedServer.getPlayerList() - .getPlayer(integratedServer.getSingleplayerProfile().getId()) + (worldBlockEntity.getType() == BlockEntityTypes.ENDER_CHEST ? integratedServer.getPlayerList() + .getPlayer(integratedServer.getSingleplayerProfile().id()) .getEnderChestInventory() : null); if (container == null) diff --git a/src/main/java/com/ldtteam/blockui/mod/item/BlockStatePipRenderer.java b/src/main/java/com/ldtteam/blockui/mod/item/BlockStatePipRenderer.java new file mode 100644 index 00000000..c5e2d6c9 --- /dev/null +++ b/src/main/java/com/ldtteam/blockui/mod/item/BlockStatePipRenderer.java @@ -0,0 +1,335 @@ +package com.ldtteam.blockui.mod.item; + +import com.ldtteam.blockui.BOGuiGraphics; +import com.ldtteam.blockui.UiRenderMacros; +import com.ldtteam.blockui.controls.ItemIcon; +import com.ldtteam.blockui.mod.BlockUI; +import com.ldtteam.blockui.mod.item.BlockStatePipRenderer.BlockStateRenderState; +import com.ldtteam.blockui.util.SingleBlockGetter.SingleBlockNeighborhood; +import com.mojang.blaze3d.platform.Lighting; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.PoseStack.Pose; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.pip.PictureInPictureRenderer; +import net.minecraft.client.renderer.Sheets; +import net.minecraft.client.renderer.block.BlockModelRenderState; +import net.minecraft.client.renderer.block.FluidRenderer; +import net.minecraft.client.renderer.block.FluidStateModelSet; +import net.minecraft.client.renderer.block.model.BlockDisplayContext; +import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.chunk.ChunkSectionLayer; +import net.minecraft.client.renderer.item.ItemStackRenderState; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.state.gui.pip.PictureInPictureRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.resources.model.cuboid.ItemTransform; +import net.minecraft.core.BlockPos; +import net.minecraft.util.LightCoordsUtil; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.client.fluid.CustomFluidRenderer; +import net.neoforged.neoforge.client.model.pipeline.VertexConsumerWrapper; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix3x2f; +import org.joml.Vector3f; +import org.joml.Vector4f; +import java.util.function.Predicate; + +// TODO: port 21.6 this is super extra overkill perf-wise - lags when rendering more than 100 instances +// but logically is most correct version (given sanity) +// ideally we would just grab the model geometry and render it as normal textures +// and not this texture PiP non-sense that costs way to much for simple block render +public class BlockStatePipRenderer extends PictureInPictureRenderer +{ + // TODO: Static instance should be fine since gui rendering is on single thread + private static final BlockDisplayContext BLOCK_DISPLAY_CONTEXT = BlockDisplayContext.create(); + private static final SingleBlockNeighborhood NEIGHBORHOOD = new SingleBlockNeighborhood(); + private @Nullable BlockStateRenderingData lastData = null; + + @Override + public Class getRenderStateClass() + { + return BlockStateRenderState.class; + } + + @Override + protected void renderToTexture(final BlockStateRenderState renderState, + final PoseStack poseStack, + final SubmitNodeCollector submitNodeCollector) + { + final BlockStateRenderingData data = renderState.data; + lastData = data; + + // prepare pose just like itemStack rendering would do + // INLINE: notes for poseStack comes roughly from OversizedItemRenderer vanilla PiP + + poseStack.pushPose(); + poseStack + .scale(BlockStateRenderState.RENDER_SIZE_F, -BlockStateRenderState.RENDER_SIZE_F, -BlockStateRenderState.RENDER_SIZE_F); + + // INLINE: itemModel()#submit(...) tranformations, we assume first layer makes sense.. + renderState.itemModel().firstLayer().itemTransform.apply(false, poseStack.last()); + poseStack.last().mulPose(renderState.itemModel().firstLayer().localTransform); + + // render block and BE + Minecraft.getInstance().gameRenderer.lighting().setupFor(Lighting.Entry.ITEMS_FLAT); + final int light = LightCoordsUtil.pack(15, 15); + renderState.blockModel.submit(poseStack, submitNodeCollector, light, OverlayTexture.NO_OVERLAY, 0); + + if (renderState.blockEntityModel() != null) + { + try + { + final var state = renderState.blockEntityModel(); + final var renderer = Minecraft.getInstance().getBlockEntityRenderDispatcher().getRenderer(state); + renderer.submit(state, poseStack, submitNodeCollector, null); + } + catch (final Exception e) + { + // well, noop then + } + } + + // render fluid + + final FluidState fluidState = data.blockState().getFluidState(); + if (!fluidState.isEmpty()) + { + final FluidStateModelSet fluidModelSet = Minecraft.getInstance().getModelManager().getFluidStateModelSet(); + final FluidRenderer fluidRenderer = new FluidRenderer(fluidModelSet); + + final var customRenderer = fluidModelSet.get(fluidState).customRenderer(); + + // losely based on block rendering, cuz ChunkSectionLayer stupid + // solid + cutout pass + submitNodeCollector + .submitCustomGeometry(poseStack, + Sheets.cutoutBlockItemSheet(), + (pose, buffer) -> renderFluid(data, + fluidState, + fluidRenderer, + customRenderer, + layer -> layer != ChunkSectionLayer.TRANSLUCENT, + buffer, + pose)); + // translucent pass + submitNodeCollector + .submitCustomGeometry(poseStack, + Sheets.translucentBlockItemSheet(), + (pose, buffer) -> renderFluid(data, + fluidState, + fluidRenderer, + customRenderer, + layer -> layer == ChunkSectionLayer.TRANSLUCENT, + buffer, + pose)); + } + + poseStack.popPose(); + } + + private void renderFluid(final BlockStateRenderingData data, + final FluidState fluidState, + final FluidRenderer fluidRenderer, + final @Nullable CustomFluidRenderer customRenderer, + final Predicate targetLayer, + final VertexConsumer buffer, + final PoseStack.Pose pose) + { + final FluidRenderer.Output output = + layer -> targetLayer.test(layer) ? new PoseTransformingVertexConsumer(buffer, pose) : NoopVertexConsumer.INSTANCE; + NEIGHBORHOOD.blockState = data.blockState(); + NEIGHBORHOOD.blockEntity = data.blockEntity(); + if (customRenderer == null || + !customRenderer.renderFluid(fluidRenderer, fluidState, NEIGHBORHOOD, BlockPos.ZERO, output, data.blockState())) + { + fluidRenderer.tesselate(NEIGHBORHOOD, BlockPos.ZERO, output, data.blockState(), fluidState); + } + NEIGHBORHOOD.blockState = null; + NEIGHBORHOOD.blockEntity = null; + } + + @Override + protected String getTextureLabel() + { + return BlockUI.resLoc("blockstate").toString(); + } + + @Override + protected float getTranslateY(final int height, final int guiScale) + { + return height / 2; + } + + @Override + protected boolean textureIsReadyToBlit(final BlockStateRenderState renderState) + { + return renderState.data == lastData; + } + + public record BlockStateRenderState(Matrix3x2f pose, + int x0, + int x1, + int y0, + int y1, + float scale, + BlockModelRenderState blockModel, + ItemStackRenderState itemModel, + @Nullable BlockEntityRenderState blockEntityModel, + BlockStateRenderingData data, + @Nullable ScreenRectangle bounds, + @Nullable ScreenRectangle scissorArea) implements PictureInPictureRenderState + { + public static final int SCALE_FACTOR = 4; + public static final int RENDER_SIZE_I = ItemIcon.DEFAULT_ITEMSTACK_SIZE_I * SCALE_FACTOR; + public static final float RENDER_SIZE_F = (float) RENDER_SIZE_I; + + public static void submit(final BOGuiGraphics target, final BlockStateRenderingData data, final ItemStack itemStack) + { + final int x = 0, y = 0; + final int w = RENDER_SIZE_I, h = RENDER_SIZE_I; + UiRenderMacros.innerSubmit(target, x, y, w, h, (pose, bounds, scissors) -> { + final BlockModelRenderState blockModel = new BlockModelRenderState(); + target.minecraft.getBlockModelResolver().update(blockModel, data.blockState(), BLOCK_DISPLAY_CONTEXT); + + final ItemStackRenderState itemModel = new ItemStackRenderState(); + // target.minecraft.getItemModelResolver() + // .updateForLiving(itemModel, itemStack, ItemDisplayContext.GUI, Minecraft.getInstance().player); + + if (itemModel.firstLayer().itemTransform.equals(ItemTransform.NO_TRANSFORM) || + data.blockState().getRenderShape() == RenderShape.INVISIBLE || + true) + { + // well, some items are bit dumb + // TODO: port 26.1 + // we now force hard default cuboid tranformation for everything + // which works a bit better than before + // but block/cross models are still terrible + // solution would be to find out which things need localTransform applied + // then remove the itemRenderState as whole since we are only using it for transformation now + target.minecraft.getItemModelResolver() + .updateForLiving(itemModel, + new ItemStack(Blocks.STONE), + ItemDisplayContext.GUI, + Minecraft.getInstance().player); + } + + BlockEntityRenderState blockEntityModel = null; + if (data.blockEntity() != null) + { + final var renderer = target.minecraft.getBlockEntityRenderDispatcher().getRenderer(data.blockEntity()); + if (renderer != null) + { + blockEntityModel = target.getFakeLevel() + .useFakeLevelContext(data.blockState(), data.blockEntity(), target.minecraft.level, fakeLevel -> { + final BlockEntityRenderState state = renderer.createRenderState(); + renderer.extractRenderState(data + .blockEntity(), state, 0, Vec3.atCenterOf(data.blockEntity().getBlockPos()), null); + return state; + }); + } + } + target.submitPictureInPictureRenderState(new BlockStateRenderState(pose, + x, + x + w, + y, + y + h, + 1, + blockModel, + itemModel, + blockEntityModel, + data, + bounds, + scissors)); + }); + } + } + + private static final class NoopVertexConsumer implements VertexConsumer + { + private static final NoopVertexConsumer INSTANCE = new NoopVertexConsumer(); + + @Override + public VertexConsumer addVertex(final float x, final float y, final float z) + { + return this; + } + + @Override + public VertexConsumer setColor(final int r, final int g, final int b, final int a) + { + return this; + } + + @Override + public VertexConsumer setColor(final int color) + { + return this; + } + + @Override + public VertexConsumer setUv(final float u, final float v) + { + return this; + } + + @Override + public VertexConsumer setUv1(final int u, final int v) + { + return this; + } + + @Override + public VertexConsumer setUv2(final int u, final int v) + { + return this; + } + + @Override + public VertexConsumer setNormal(final float x, final float y, final float z) + { + return this; + } + + @Override + public VertexConsumer setLineWidth(final float width) + { + return this; + } + } + + private static final class PoseTransformingVertexConsumer extends VertexConsumerWrapper + { + private final Pose pose; + + public PoseTransformingVertexConsumer(final VertexConsumer parent, final PoseStack.Pose pose) + { + super(parent); + this.pose = pose; + } + + @Override + public VertexConsumer addVertex(final float x, final float y, final float z) + { + final var vec = new Vector4f(x, y, z, 1); + pose.pose().transform(vec); + vec.div(vec.w); + return super.addVertex(vec.x(), vec.y(), vec.z()); + } + + @Override + public VertexConsumer setNormal(final float x, final float y, final float z) + { + final var vec = new Vector3f(x, y, z); + pose.transformNormal(x, y, z, vec); + vec.normalize(); + return super.setNormal(vec.x(), vec.y(), vec.z()); + } + } +} diff --git a/src/main/java/com/ldtteam/blockui/mod/item/BlockStateRenderingData.java b/src/main/java/com/ldtteam/blockui/mod/item/BlockStateRenderingData.java index 8e7d8185..9e5c8b5d 100644 --- a/src/main/java/com/ldtteam/blockui/mod/item/BlockStateRenderingData.java +++ b/src/main/java/com/ldtteam/blockui/mod/item/BlockStateRenderingData.java @@ -3,24 +3,14 @@ import com.ldtteam.blockui.mod.Log; import com.ldtteam.common.util.BlockToItemHelper; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.block.BlockModelShaper; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.MultiVariant; -import net.minecraft.client.resources.model.ModelBakery; -import net.minecraft.client.resources.model.ModelResourceLocation; -import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.Direction.Axis; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.neoforged.neoforge.client.model.data.ModelData; +import net.neoforged.neoforge.model.data.ModelData; import net.neoforged.neoforge.common.util.Lazy; import org.jetbrains.annotations.Nullable; import java.util.function.Function; @@ -131,53 +121,12 @@ public ItemStack itemStack() /** * @return true if model contains only Y axis rotations - * TODO: move to tag */ public static boolean checkModelForYrotation(final BlockState blockState) { - final ModelResourceLocation modelResLoc = BlockModelShaper.stateToModelLocation(blockState); - final ModelBakery modelBakery = - Minecraft.getInstance().getBlockRenderer().getBlockModelShaper().getModelManager().getModelBakery(); - final UnbakedModel model = modelBakery.topLevelModels.get(modelResLoc); - final BlockModel blockModel = model instanceof final BlockModel bm ? bm : - (model instanceof final MultiVariant mv ? - modelBakery.modelResources.get(ModelBakery.MODEL_LISTER.idToFile(mv.getVariants().get(0).getModelLocation())) : - null); - - if (blockModel == null || blockModel.getElements().isEmpty()) - { - return false; - } - - int headCountOfRotated = 0; - for (final BlockElement element : blockModel.getElements()) - { - if (element.rotation != null && element.rotation.axis() == Direction.Axis.Y) - { - headCountOfRotated++; - } - else - { - break; - } - } - // blind guess: if majority is rotation Y then fine - if (headCountOfRotated == 0) - { - return false; - } - - if (blockState.hasProperty(BlockStateProperties.AXIS)) - { - return blockState.getValue(BlockStateProperties.AXIS) == Axis.Y; - } - - if (blockState.hasProperty(BlockStateProperties.FACING)) - { - final Direction facing = blockState.getValue(BlockStateProperties.FACING); - return facing == Direction.UP || facing == Direction.DOWN; - } - - return true; + // TODO: port 21.6 this is completely gone + // find out how to detect whether blockState is being rendered by model:block/cross + // or why cross models have rotation issues + return false; } } diff --git a/src/main/java/com/ldtteam/blockui/util/SafeError.java b/src/main/java/com/ldtteam/blockui/util/SafeError.java index 00ebc00d..2d875790 100644 --- a/src/main/java/com/ldtteam/blockui/util/SafeError.java +++ b/src/main/java/com/ldtteam/blockui/util/SafeError.java @@ -1,7 +1,9 @@ package com.ldtteam.blockui.util; import com.ldtteam.blockui.mod.Log; +import net.minecraft.util.Util; import net.neoforged.fml.loading.FMLEnvironment; +import java.util.Objects; /** * Utility class for throwing errors which is safe during production. @@ -21,7 +23,36 @@ public static void throwInDev(final RuntimeException exception) } else { - throw exception; + throw Util.pauseInIde(exception); } } + + /** + * @param value the object reference to check for nullity + * @param errorMessage detail message to be used in the event that a {@code NullPointerException} is thrown + * @see Objects#requireNonNull(Object, String) + */ + public static void requireNonNull(final Object value, final String errorMessage) + { + if (value == null) + { + throwInDev(new NullPointerException(errorMessage)); + } + } + + /** + * @param value the object reference to check for nullity + * @param defaultValue default value for production environment + * @param errorMessage detail message to be used in the event that a {@code NullPointerException} is thrown + * @see Objects#requireNonNull(Object, String) + */ + public static T requireNonNull(final T value, final T defaultValue, final String errorMessage) + { + if (value == null) + { + throwInDev(new NullPointerException(errorMessage)); + return defaultValue; + } + return value; + } } diff --git a/src/main/java/com/ldtteam/blockui/util/SingleBlockGetter.java b/src/main/java/com/ldtteam/blockui/util/SingleBlockGetter.java index c56a421e..54b6d94d 100644 --- a/src/main/java/com/ldtteam/blockui/util/SingleBlockGetter.java +++ b/src/main/java/com/ldtteam/blockui/util/SingleBlockGetter.java @@ -1,10 +1,11 @@ package com.ldtteam.blockui.util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.BlockAndTintGetter; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.core.registries.Registries; -import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.CardinalLighting; import net.minecraft.world.level.ColorResolver; import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.biome.Biomes; @@ -13,7 +14,6 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.lighting.LevelLightEngine; import net.minecraft.world.level.material.FluidState; -import net.neoforged.neoforge.server.ServerLifecycleHooks; import javax.annotation.Nullable; /** @@ -66,7 +66,7 @@ public int getHeight() } @Override - public int getMinBuildHeight() + public int getMinY() { return 0; } @@ -92,33 +92,36 @@ public SingleBlockNeighborhood() } @Override - public float getShade(final Direction direction, final boolean shade) + public LevelLightEngine getLightEngine() { - return 1; + throw new UnsupportedOperationException("Does anyone need LightEngine?"); } @Override - public LevelLightEngine getLightEngine() + public int getBrightness(final LightLayer lightLayer, final BlockPos pos) { - throw new UnsupportedOperationException("Does anyone need LightEngine?"); + return 10; } @Override - public int getBlockTint(final BlockPos pos, final ColorResolver colorResolver) + public int getRawBrightness(final BlockPos pos, final int amount) { - return colorResolver.getColor(ServerLifecycleHooks.getCurrentServer().registryAccess().registryOrThrow(Registries.BIOME).getOrThrow(Biomes.PLAINS), pos.getX(), pos.getZ()); + return 10; } @Override - public int getBrightness(final LightLayer lightLayer, final BlockPos pos) + public CardinalLighting cardinalLighting() { - return 10; + return CardinalLighting.DEFAULT; } @Override - public int getRawBrightness(final BlockPos pos, final int amount) + public int getBlockTint(final BlockPos pos, final ColorResolver color) { - return 10; + return color.getColor( + Minecraft.getInstance().level.registryAccess().lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.PLAINS).value(), + 0, + 0); } } } diff --git a/src/main/java/com/ldtteam/blockui/util/SpacerTextComponent.java b/src/main/java/com/ldtteam/blockui/util/SpacerTextComponent.java index d92e0052..dd907af2 100644 --- a/src/main/java/com/ldtteam/blockui/util/SpacerTextComponent.java +++ b/src/main/java/com/ldtteam/blockui/util/SpacerTextComponent.java @@ -27,9 +27,8 @@ public FormattedCharSequence getVisualOrderText() return new FormattedSpacerComponent(pixelHeight); } - @Override - public MapCodec codec() + public MapCodec codec() { return CODEC; } diff --git a/src/main/java/com/ldtteam/blockui/util/ToggleableTextComponent.java b/src/main/java/com/ldtteam/blockui/util/ToggleableTextComponent.java index b7277e82..6f583833 100644 --- a/src/main/java/com/ldtteam/blockui/util/ToggleableTextComponent.java +++ b/src/main/java/com/ldtteam/blockui/util/ToggleableTextComponent.java @@ -1,6 +1,5 @@ package com.ldtteam.blockui.util; -import com.ldtteam.blockui.mod.BlockUI; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; @@ -20,7 +19,6 @@ public record ToggleableTextComponent(BooleanSupplier condition, MutableComponen .group(ComponentSerialization.CODEC.fieldOf("data").forGetter(ToggleableTextComponent::data), Codec.BOOL.fieldOf("condition").forGetter(comp -> comp.condition().getAsBoolean())) .apply(instance, (data, conditionValue) -> new ToggleableTextComponent(() -> conditionValue, (MutableComponent) data))); - public static final ComponentContents.Type TYPE = new ComponentContents.Type<>(CODEC, BlockUI.MOD_ID + "_toggle"); /** * @param condition if contidition returns true then data will get rendered @@ -46,9 +44,9 @@ public FormattedCharSequence getVisualOrderText() } @Override - public Type type() + public MapCodec codec() { - return TYPE; + return CODEC; } public record FormattedToggleableCharSequence(BooleanSupplier condition, FormattedCharSequence data) implements FormattedCharSequence diff --git a/src/main/java/com/ldtteam/blockui/util/color/ColourARGB.java b/src/main/java/com/ldtteam/blockui/util/color/ColourARGB.java index 0c0dbe33..05ed5792 100644 --- a/src/main/java/com/ldtteam/blockui/util/color/ColourARGB.java +++ b/src/main/java/com/ldtteam/blockui/util/color/ColourARGB.java @@ -5,28 +5,33 @@ */ public record ColourARGB(int argb) implements IColour { + public ColourARGB(int rgb, int alpha) + { + this((rgb & 0x00ffffff) | ((alpha & MAX_INT_VALUE) << 24)); + } + @Override public int alpha() { - return (argb >> 24) & 0xff; + return (argb >> 24) & MAX_INT_VALUE; } @Override public int red() { - return (argb >> 16) & 0xff; + return (argb >> 16) & MAX_INT_VALUE; } @Override public int green() { - return (argb >> 8) & 0xff; + return (argb >> 8) & MAX_INT_VALUE; } @Override public int blue() { - return (argb >> 0) & 0xff; + return (argb >> 0) & MAX_INT_VALUE; } @Override diff --git a/src/main/java/com/ldtteam/blockui/util/color/ColourQuartet4f.java b/src/main/java/com/ldtteam/blockui/util/color/ColourQuartet4f.java new file mode 100644 index 00000000..dc2ebb1a --- /dev/null +++ b/src/main/java/com/ldtteam/blockui/util/color/ColourQuartet4f.java @@ -0,0 +1,62 @@ +package com.ldtteam.blockui.util.color; + +import org.joml.Vector4f; + +/** + * Colour backed by four separated channel values. Generally good choice for rendering related things + */ +public record ColourQuartet4f(float redF, float greenF, float blueF, float alphaF) implements IColour +{ + public ColourQuartet4f(final Vector4f color) + { + this(color.x, color.y, color.z, color.w); + } + + @Override + public int red() + { + return IColour.asInt(redF); + } + + @Override + public int green() + { + return IColour.asInt(greenF); + } + + @Override + public int blue() + { + return IColour.asInt(blueF); + } + + @Override + public int alpha() + { + return IColour.asInt(alphaF); + } + + @Override + public float alphaF() + { + return alphaF; + } + + @Override + public int argb() + { + return (alpha() << 24) | (red() << 16) | (green() << 8) | (blue() << 0); + } + + @Override + public int rgba() + { + return (red() << 24) | (green() << 16) | (blue() << 8) | (alpha() << 0); + } + + @Override + public ColourQuartet4f asFloatQuartet() + { + return this; + } +} diff --git a/src/main/java/com/ldtteam/blockui/util/color/ColourQuartet.java b/src/main/java/com/ldtteam/blockui/util/color/ColourQuartet4i.java similarity index 77% rename from src/main/java/com/ldtteam/blockui/util/color/ColourQuartet.java rename to src/main/java/com/ldtteam/blockui/util/color/ColourQuartet4i.java index c8f13cc9..b4bdcd93 100644 --- a/src/main/java/com/ldtteam/blockui/util/color/ColourQuartet.java +++ b/src/main/java/com/ldtteam/blockui/util/color/ColourQuartet4i.java @@ -3,7 +3,7 @@ /** * Colour backed by four separated channel values. Generally good choice for rendering related things */ -public record ColourQuartet(int red, int green, int blue, int alpha) implements IColour +public record ColourQuartet4i(int red, int green, int blue, int alpha) implements IColour { @Override public int argb() @@ -18,7 +18,7 @@ public int rgba() } @Override - public ColourQuartet asQuartet() + public ColourQuartet4i asIntQuartet() { return this; } diff --git a/src/main/java/com/ldtteam/blockui/util/color/ColourRGBA.java b/src/main/java/com/ldtteam/blockui/util/color/ColourRGBA.java index f4584fef..a3dd6c01 100644 --- a/src/main/java/com/ldtteam/blockui/util/color/ColourRGBA.java +++ b/src/main/java/com/ldtteam/blockui/util/color/ColourRGBA.java @@ -5,28 +5,33 @@ */ public record ColourRGBA(int rgba) implements IColour { + public ColourRGBA(int rgb, int alpha) + { + this((rgb << 8) | (alpha & MAX_INT_VALUE)); + } + @Override public int red() { - return (rgba >> 24) & 0xff; + return (rgba >> 24) & MAX_INT_VALUE; } @Override public int green() { - return (rgba >> 16) & 0xff; + return (rgba >> 16) & MAX_INT_VALUE; } @Override public int blue() { - return (rgba >> 8) & 0xff; + return (rgba >> 8) & MAX_INT_VALUE; } @Override public int alpha() { - return (rgba >> 0) & 0xff; + return (rgba >> 0) & MAX_INT_VALUE; } @Override diff --git a/src/main/java/com/ldtteam/blockui/util/color/ColouredVertexConsumer.java b/src/main/java/com/ldtteam/blockui/util/color/ColouredVertexConsumer.java deleted file mode 100644 index d680f8d1..00000000 --- a/src/main/java/com/ldtteam/blockui/util/color/ColouredVertexConsumer.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.ldtteam.blockui.util.color; - -import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.blaze3d.vertex.VertexFormatElement; - -/** - * Wrapper for having default color for vertex consumer - */ -public class ColouredVertexConsumer implements VertexConsumer -{ - protected final VertexConsumer parent; - public IColour defaultColor = null; - - public ColouredVertexConsumer(final VertexConsumer parent) - { - this.parent = parent; - } - - @Override - public ColouredVertexConsumer addVertex(final float x, final float y, final float z) - { - parent.addVertex(x, y, z); - return this; - } - - @Override - public ColouredVertexConsumer setColor(final int r, final int g, final int b, final int a) - { - parent.setColor(r, g, b, a); - return this; - } - - /** - * Applies previously set defaultColor, will shamelessly NPE if you forgot to set it - */ - public ColouredVertexConsumer setDefaultColor() - { - defaultColor.writeIntoBuffer(this); - return this; - } - - @Override - public ColouredVertexConsumer setUv(final float u, final float v) - { - parent.setUv(u, v); - return this; - } - - @Override - public ColouredVertexConsumer setUv1(final int u, final int v) - { - parent.setUv1(u, v); - return this; - } - - @Override - public ColouredVertexConsumer setUv2(final int u, final int v) - { - parent.setUv2(u, v); - return this; - } - - @Override - public ColouredVertexConsumer setNormal(final float x, final float y, final float z) - { - parent.setNormal(x, y, z); - return this; - } - - @Override - public ColouredVertexConsumer misc(final VertexFormatElement element, final int... values) - { - parent.misc(element, values); - return this; - } -} diff --git a/src/main/java/com/ldtteam/blockui/util/color/IColour.java b/src/main/java/com/ldtteam/blockui/util/color/IColour.java index 176e0c8d..8e86cf03 100644 --- a/src/main/java/com/ldtteam/blockui/util/color/IColour.java +++ b/src/main/java/com/ldtteam/blockui/util/color/IColour.java @@ -1,9 +1,24 @@ package com.ldtteam.blockui.util.color; import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.network.chat.TextColor; public interface IColour { + // channel transformations + public static final float MAX_FLOAT_VALUE = 255.0f; + public static final int MAX_INT_VALUE = 255; + + public static float asFloat(final int value) + { + return value / MAX_FLOAT_VALUE; + } + + public static int asInt(final float value) + { + return (int) Math.floor(value * MAX_FLOAT_VALUE); + } + /** * @return red channel only, range 0-255 */ @@ -29,7 +44,7 @@ public interface IColour */ default float alphaF() { - return alpha() / 255.0f; + return alpha() / MAX_FLOAT_VALUE; } /** @@ -45,9 +60,17 @@ default float alphaF() /** * @return quartet instance or this instance (if already in quartet format) */ - default ColourQuartet asQuartet() + default ColourQuartet4i asIntQuartet() + { + return new ColourQuartet4i(red(), green(), blue(), alpha()); + } + + /** + * @return quartet instance or this instance (if already in quartet format) + */ + default ColourQuartet4f asFloatQuartet() { - return new ColourQuartet(red(), green(), blue(), alpha()); + return new ColourQuartet4f(red(), green(), blue(), alpha()); } /** @@ -73,4 +96,54 @@ default void writeIntoBuffer(final VertexConsumer buffer) { buffer.setColor(red(), green(), blue(), alpha()); } + + default TextColor toTextColor() + { + return TextColor.fromRgb(argb()); + } + + public static final IColour ZERO = new IColour() + { + @Override + public int red() + { + return 0; + } + + @Override + public int green() + { + return 0; + } + + @Override + public int blue() + { + return 0; + } + + @Override + public int alpha() + { + return 0; + } + + @Override + public int argb() + { + return 0; + } + + @Override + public int rgba() + { + return 0; + } + + @Override + public void writeIntoBuffer(VertexConsumer buffer) + { + // intentionally skip + } + }; } diff --git a/src/main/java/com/ldtteam/blockui/util/cursor/Cursor.java b/src/main/java/com/ldtteam/blockui/util/cursor/Cursor.java new file mode 100644 index 00000000..d83e8866 --- /dev/null +++ b/src/main/java/com/ldtteam/blockui/util/cursor/Cursor.java @@ -0,0 +1,99 @@ +package com.ldtteam.blockui.util.cursor; + +import com.ldtteam.blockui.mod.BlockUI; +import com.ldtteam.blockui.util.SafeError; +import com.ldtteam.blockui.util.texture.CursorTexture; +import com.ldtteam.blockui.util.texture.IsOurTexture; +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.platform.cursor.CursorType; +import com.mojang.blaze3d.platform.cursor.CursorTypes; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.resources.Identifier; +import org.lwjgl.glfw.GLFW; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +/** + * Interface to wrap various cursors + */ +public class Cursor +{ + private static final Logger LOGGER = LoggerFactory.getLogger(Cursor.class); + + /** Probably arrow, but OS dependend */ + public static final CursorType DEFAULT = CursorType.DEFAULT; + public static final CursorType ARROW = CursorTypes.ARROW; + public static final CursorType TEXT_CURSOR = CursorTypes.IBEAM; + public static final CursorType CROSSHAIR = CursorTypes.CROSSHAIR; + public static final CursorType HAND = CursorTypes.POINTING_HAND; + public static final CursorType HORIZONTAL_RESIZE = CursorTypes.RESIZE_EW; + public static final CursorType VERTICAL_RESIZE = CursorTypes.RESIZE_NS; + public static final CursorType RESIZE_NWSE = CursorType.createStandardCursor(GLFW.GLFW_RESIZE_NWSE_CURSOR, "resize_nwse", Cursor.DEFAULT); + public static final CursorType RESIZE_NESW = CursorType.createStandardCursor(GLFW.GLFW_RESIZE_NESW_CURSOR, "resize_nesw", Cursor.DEFAULT); + public static final CursorType RESIZE = CursorTypes.RESIZE_ALL; + public static final CursorType NOT_ALLOWED = CursorTypes.NOT_ALLOWED; + + private static final Map CURSOR_MAP = Map.ofEntries( + Map.entry(DEFAULT.name, DEFAULT), + Map.entry(ARROW.name, ARROW), + Map.entry(TEXT_CURSOR.name, TEXT_CURSOR), + Map.entry(CROSSHAIR.name, CROSSHAIR), + Map.entry(HAND.name, HAND), + Map.entry(HORIZONTAL_RESIZE.name, HORIZONTAL_RESIZE), + Map.entry(VERTICAL_RESIZE.name, VERTICAL_RESIZE), + Map.entry(RESIZE_NWSE.name, RESIZE_NWSE), + Map.entry(RESIZE_NESW.name, RESIZE_NESW), + Map.entry(RESIZE.name, RESIZE), + Map.entry(NOT_ALLOWED.name, NOT_ALLOWED) + ); + + public static CursorType of(final Identifier resLoc) + { + if ((BlockUI.MOD_ID + "_std").equalsIgnoreCase(resLoc.getNamespace())) + { + return SafeError.requireNonNull(CURSOR_MAP.get(resLoc.getPath()), Cursor.DEFAULT, "Invalid built-in cursor: " + resLoc.toString()); + } + + final TextureManager texManager = Minecraft.getInstance().getTextureManager(); + final AbstractTexture texture = texManager.getTexture(resLoc); + if (!(texture instanceof CursorTexture)) + { + if (IsOurTexture.isOur(texture)) + { + LOGGER.warn("Trying to use special BlockUI texture as cursor? Things may not work well: " + resLoc.toString()); + } + + texManager.registerAndLoad(resLoc, new CursorTexture(resLoc)); + } + + return new TexturedCursorType(resLoc); + } + + public static class TexturedCursorType extends CursorType + { + private final Identifier resLoc; + + protected TexturedCursorType(final Identifier resLoc) + { + super(BlockUI.MOD_ID + "_tex_cursor:" + resLoc.toString(), -1L); + this.resLoc = resLoc; + } + + @Override + public void select(final Window window) + { + final AbstractTexture texture = Minecraft.getInstance().getTextureManager().getTexture(resLoc); + + if (!(texture instanceof final CursorTexture cursorTexture)) + { + throw new IllegalArgumentException("Did you forget to load CursorTexture (or create CursorType) for: " + resLoc); + } + + GLFW.glfwSetCursor(window.handle(), cursorTexture.getGlfwCursorAddress()); + } + } +} diff --git a/src/main/java/com/ldtteam/blockui/util/resloc/OutOfJarResourceLocation.java b/src/main/java/com/ldtteam/blockui/util/resloc/OutOfJarResourceLocation.java index 91fd700c..62574248 100644 --- a/src/main/java/com/ldtteam/blockui/util/resloc/OutOfJarResourceLocation.java +++ b/src/main/java/com/ldtteam/blockui/util/resloc/OutOfJarResourceLocation.java @@ -2,14 +2,16 @@ import com.mojang.authlib.GameProfile; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.core.ClientAsset.Texture; import net.minecraft.resources.Identifier; import net.minecraft.server.packs.PackResources; +import net.minecraft.server.packs.repository.KnownPack; import net.minecraft.server.packs.resources.FallbackResourceManager; import net.minecraft.server.packs.resources.IoSupplier; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.ResourceMetadata; +import net.minecraft.world.entity.player.PlayerSkin; import org.jetbrains.annotations.Nullable; import java.io.BufferedReader; import java.io.FileNotFoundException; @@ -17,6 +19,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.UnaryOperator; @@ -37,7 +40,6 @@ public static OutOfJarResourceLocation of(final String namespace, final Path pat return new OutOfJarResourceLocation(namespace, path, null); } - @SuppressWarnings("resource") public static OutOfJarResourceLocation ofMinecraftFolder(final String namespace, final String... parts) { Path path = Minecraft.getInstance().gameDirectory.toPath().resolve(namespace); @@ -51,28 +53,17 @@ public static OutOfJarResourceLocation ofMinecraftFolder(final String namespace, /** * @param minecraft minecraft instance * @param gameProfile player profile - * @param textureSelector null for {@code PlayerSkin#texture()}, or {@code PlayerSkin#capeTexture()} or - * {@code PlayerSkin#elytraTexture()} - both cape and elytry may return null future + * @param textureSelector null for {@link PlayerSkin#body()}, or {@link PlayerSkin#cape()} or + * {@link PlayerSkin#elytra()} - both cape and elytry may return null future */ public static CompletableFuture ofMinecraftSkin(final Minecraft minecraft, final GameProfile gameProfile, - @Nullable final Function textureSelector) + @Nullable final Function textureSelector) { - return minecraft.getSkinManager().getOrLoad(gameProfile).thenApply(playerSkin -> { - final Identifier skinResLoc = textureSelector == null ? playerSkin.texture() : textureSelector.apply(playerSkin); - if (skinResLoc == null) - { - return null; - } - - final AbstractTexture texture = minecraft.getTextureManager().getTexture(skinResLoc); - if (!(texture instanceof final HttpTexture httpTexture)) - { - return skinResLoc; - } - - return new OutOfJarResourceLocation(skinResLoc.getNamespace(), httpTexture.file.toPath(), skinResLoc.getPath()); - }); + return minecraft.getSkinManager() + .get(gameProfile) + .thenApply( + skin -> skin.map(textureSelector == null ? PlayerSkin::body : textureSelector).map(Texture::texturePath).orElse(null)); } public Path getNioPath() @@ -93,8 +84,9 @@ public static Resource getResourceHandle(final Identifier resLoc, final Resource { if (resLoc instanceof final OutOfJarResourceLocation nioResLoc) { - return fileExists(nioResLoc.withSuffix(".mcmeta"), fallbackManager) ? - new OutOfJarResource(nioResLoc, FallbackResourceManager.convertToMetadata(() -> Files.newInputStream(nioResLoc.getNioPath()))) : + final OutOfJarResourceLocation mcmeta = nioResLoc.withSuffix(".mcmeta"); + return fileExists(mcmeta, fallbackManager) ? + new OutOfJarResource(nioResLoc, FallbackResourceManager.convertToMetadata(() -> Files.newInputStream(mcmeta.getNioPath()))) : new OutOfJarResource(nioResLoc); } return fallbackManager.getResource(resLoc).orElseThrow(() -> new FileNotFoundException("File not found: " + resLoc)); @@ -123,7 +115,7 @@ public static BufferedReader openReader(final Identifier resLoc, final ResourceM */ @Override @Deprecated(forRemoval = false) - public Identifier withPath(final String path) + public OutOfJarResourceLocation withPath(final String path) { return of(getNamespace(), Path.of(path)); } @@ -133,7 +125,7 @@ public Identifier withPath(final String path) */ @Override @Deprecated(forRemoval = false) - public Identifier withPath(final UnaryOperator op) + public OutOfJarResourceLocation withPath(final UnaryOperator op) { return of(getNamespace(), Path.of(op.apply(nioPath.toString()))); } @@ -142,7 +134,7 @@ public Identifier withPath(final UnaryOperator op) * With path prefix (prefix + current path) */ @Override - public Identifier withPrefix(final String prefix) + public OutOfJarResourceLocation withPrefix(final String prefix) { return of(getNamespace(), Path.of(prefix).resolve(nioPath)); } @@ -154,7 +146,7 @@ public Identifier withPrefix(final String prefix) * would add file ".foo" in subdirectory. To get same behaviour as {@link Path#resolve(Path)} add '/' to start of parameter */ @Override - public Identifier withSuffix(final String suffix) + public OutOfJarResourceLocation withSuffix(final String suffix) { // in nio resolveSibling(...) = parent + path.of(...) so theoretically should resolve both correctly return of(getNamespace(), nioPath.resolveSibling(nioPath.getFileName().toString() + suffix)); @@ -225,6 +217,13 @@ public PackResources source() return null; } + @Override + public Optional knownPackInfo() + { + // currently only used at one place, that treats empty as Lifecycle.experimental() which seems fine + return Optional.empty(); + } + @Override public String sourcePackId() { diff --git a/src/main/java/com/ldtteam/blockui/util/texture/CursorTexture.java b/src/main/java/com/ldtteam/blockui/util/texture/CursorTexture.java new file mode 100644 index 00000000..5875a00b --- /dev/null +++ b/src/main/java/com/ldtteam/blockui/util/texture/CursorTexture.java @@ -0,0 +1,126 @@ +package com.ldtteam.blockui.util.texture; + +import com.ldtteam.blockui.Pane; +import com.ldtteam.blockui.mod.BlockUI; +import com.ldtteam.blockui.util.resloc.OutOfJarResourceLocation; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.platform.NativeImage.Format; +import com.mojang.blaze3d.platform.cursor.CursorType; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.client.renderer.texture.ReloadableTexture; +import net.minecraft.client.renderer.texture.TextureContents; +import net.minecraft.client.resources.metadata.texture.TextureMetadataSection; +import net.minecraft.resources.Identifier; +import net.minecraft.server.packs.metadata.MetadataSectionType; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWImage; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; + +/** + * Used for textured cursors. + * + * @see Pane#setCursor(CursorType) + */ +public class CursorTexture extends ReloadableTexture +{ + private static final Logger LOGGER = LoggerFactory.getLogger(CursorTexture.class); + + private CursorMetadataSection cursorMetadata = CursorMetadataSection.EMPTY; + private long glfwCursorAddress = 0; + + public CursorTexture(final Identifier resLoc) + { + super(resLoc); + } + + @Override + public TextureContents loadContents(final ResourceManager resourceManager) throws IOException + { + final Resource resource = OutOfJarResourceLocation.getResourceHandle(resourceId(), resourceManager); + final NativeImage nativeImage; + try (var is = resource.open()) + { + nativeImage = NativeImage.read(is); + } + if (nativeImage.format() != Format.RGBA) + { + LOGGER.error("Cannot load texture for cursor as it is not in RGBA format, resource location: " + resourceId()); + + nativeImage.close(); + return TextureContents.createMissing(); + } + + this.cursorMetadata = resource.metadata().getSection(CursorMetadataSection.TYPE).orElse(CursorMetadataSection.EMPTY); + + return new TextureContents(nativeImage, resource.metadata().getSection(TextureMetadataSection.TYPE).orElse(null)); + } + + @Override + public void apply(final TextureContents contents) + { + try (NativeImage nativeImage = contents.image()) + { + RenderSystem.assertOnRenderThread(); + + this.close(); + + try (var stack = MemoryStack.stackPush()) + { + final GLFWImage image = GLFWImage.malloc(stack); + image.width(nativeImage.getWidth()); + image.height(nativeImage.getHeight()); + MemoryUtil.memPutAddress(image.address() + GLFWImage.PIXELS, nativeImage.getPointer()); + glfwCursorAddress = GLFW.glfwCreateCursor(image, cursorMetadata.hotspotX, cursorMetadata.hotspotY); + } + + if (glfwCursorAddress == 0) + { + LOGGER.error("Cannot create textured cursor for resource location: " + resourceId()); + } + } + } + + @Override + protected void doLoad(final NativeImage image) + { + // Noop + } + + @Override + public void close() + { + if (glfwCursorAddress != 0) + { + RenderSystem.assertOnRenderThread(); + GLFW.glfwDestroyCursor(glfwCursorAddress); + glfwCursorAddress = 0; + } + super.close(); + } + + public long getGlfwCursorAddress() + { + return glfwCursorAddress; + } + + public static record CursorMetadataSection(int hotspotX, int hotspotY) + { + public static final CursorMetadataSection EMPTY = new CursorMetadataSection(0, 0); + + public static final Codec CODEC = RecordCodecBuilder.create(builder -> builder + .group(Codec.INT.optionalFieldOf("hotspot.x", 0).forGetter(CursorMetadataSection::hotspotX), + Codec.INT.optionalFieldOf("hotspot.y", 0).forGetter(CursorMetadataSection::hotspotY)) + .apply(builder, CursorMetadataSection::new)); + + public static final MetadataSectionType TYPE = + new MetadataSectionType<>("ldtteam." + BlockUI.MOD_ID + ".cursor", CODEC); + } +} diff --git a/src/main/java/com/ldtteam/blockui/util/texture/IsOurTexture.java b/src/main/java/com/ldtteam/blockui/util/texture/IsOurTexture.java index 62e8905a..33a3536f 100644 --- a/src/main/java/com/ldtteam/blockui/util/texture/IsOurTexture.java +++ b/src/main/java/com/ldtteam/blockui/util/texture/IsOurTexture.java @@ -11,6 +11,6 @@ public final class IsOurTexture */ public static final boolean isOur(final AbstractTexture texture) { - return texture instanceof OutOfJarTexture || texture instanceof SpriteTexture || texture instanceof CursorTexture; + return texture instanceof OutOfJarTexture || texture instanceof CursorTexture; } } diff --git a/src/main/java/com/ldtteam/blockui/util/texture/MissingCursorTexture.java b/src/main/java/com/ldtteam/blockui/util/texture/MissingCursorTexture.java deleted file mode 100644 index 28b450d1..00000000 --- a/src/main/java/com/ldtteam/blockui/util/texture/MissingCursorTexture.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.ldtteam.blockui.util.texture; - -import com.ldtteam.blockui.mod.BlockUI; -import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; -import net.minecraft.resources.Identifier; -import net.minecraft.server.packs.resources.ResourceManager; - -import java.io.IOException; - -/** - * Backed by missing vanilla texture. - */ -public final class MissingCursorTexture extends CursorTexture -{ - public static final MissingCursorTexture INSTANCE = new MissingCursorTexture(); - - private MissingCursorTexture() - { - super(Identifier.fromNamespaceAndPath(BlockUI.MOD_ID, "missing_cursor_texture")); - super.nativeImage = MissingTextureAtlasSprite.getTexture().getPixels(); - } - - @Override - public void close() - { - // Noop - } - - @Override - protected void destroyCursorHandle() - { - // Noop - } - - @Override - public void load(final ResourceManager resourceManager) throws IOException - { - // Noop - } -} diff --git a/src/main/java/com/ldtteam/blockui/util/texture/OutOfJarTexture.java b/src/main/java/com/ldtteam/blockui/util/texture/OutOfJarTexture.java index aa6921c0..b4509901 100644 --- a/src/main/java/com/ldtteam/blockui/util/texture/OutOfJarTexture.java +++ b/src/main/java/com/ldtteam/blockui/util/texture/OutOfJarTexture.java @@ -1,75 +1,61 @@ package com.ldtteam.blockui.util.texture; -import com.ldtteam.blockui.mod.BlockUI; import com.ldtteam.blockui.util.resloc.OutOfJarResourceLocation; import com.mojang.blaze3d.platform.NativeImage; -import com.mojang.blaze3d.platform.TextureUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.AbstractTexture; -import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; +import net.minecraft.client.renderer.texture.ReloadableTexture; import net.minecraft.client.renderer.texture.SimpleTexture; +import net.minecraft.client.renderer.texture.TextureContents; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.resources.metadata.animation.AnimationMetadataSection; import net.minecraft.client.resources.metadata.texture.TextureMetadataSection; import net.minecraft.resources.Identifier; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; -import net.neoforged.fml.loading.FMLEnvironment; + +import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.file.NoSuchFileException; /** * Inspired by {@link SimpleTexture} */ -public class OutOfJarTexture extends AbstractTexture +public class OutOfJarTexture extends ReloadableTexture { protected final OutOfJarResourceLocation resourceLocation; - private boolean redirectToSprite = false; public OutOfJarTexture(final OutOfJarResourceLocation resourceLocation) { + super(resourceLocation); this.resourceLocation = resourceLocation; } @Override - public void load(final ResourceManager resourceManager) throws IOException + public TextureContents loadContents(final ResourceManager resourceManager) throws IOException { final Resource resource = OutOfJarResourceLocation.getResourceHandle(resourceLocation, resourceManager); // redirect to sprite - if (resource.metadata().getSection(AnimationMetadataSection.SERIALIZER).isPresent()) + if (resource.metadata().getSection(AnimationMetadataSection.TYPE).isPresent()) { - redirectToSprite = true; - throw new IOException("Vanilla hack: redirecting loading to sprite texture, do NOT report this exception, it IS intended"); + throw new UnsupportedOperationException("Trying to load sprite texture without texture atlas isn't supporsed since 26.1"); // ^ throwing anything else but IO crashes client, but we need to take missing texture path (so this object dies properly) } - final TextureMetadataSection textureMeta = resource.metadata().getSection(TextureMetadataSection.SERIALIZER).orElse(null); + final TextureMetadataSection textureMeta = resource.metadata().getSection(TextureMetadataSection.TYPE).orElse(null); final NativeImage nativeImage; try (var is = resource.open()) { nativeImage = NativeImage.read(is); } - TextureUtil.prepareImage(getId(), 0, nativeImage.getWidth(), nativeImage.getHeight()); - - if (textureMeta != null) - { - nativeImage.upload(0, - 0, - 0, - 0, - 0, - nativeImage.getWidth(), - nativeImage.getHeight(), - textureMeta.isBlur(), - textureMeta.isClamp(), - false, - true); - } - else + catch (final NoSuchFileException e) { - nativeImage.upload(0, 0, 0, true); + // rethrow to java.io since vanilla stupid + throw new FileNotFoundException(e.getMessage()); } + return new TextureContents(nativeImage, textureMeta); } public static AbstractTexture assertLoadedDefaultManagers(final Identifier resLoc) @@ -79,7 +65,7 @@ public static AbstractTexture assertLoadedDefaultManagers(final Identifier resLo /** * Checks whether given resLoc should be loaded into given textureManager as outOfJar or sprite texture - * + * * @return valid texture instance (including missing texture) */ public static AbstractTexture assertLoaded(final Identifier resLoc, final TextureManager textureManager, final ResourceManager resourceManager) @@ -90,31 +76,15 @@ public static AbstractTexture assertLoaded(final Identifier resLoc, final Textur return textureManager.getTexture(resLoc); } - final AbstractTexture current = textureManager.getTexture(resLoc, null); + final AbstractTexture current = textureManager.getTexture(resLoc); if (IsOurTexture.isOur(current)) { return current; } - if (current == MissingTextureAtlasSprite.getTexture()) - { - if (!FMLEnvironment.isProduction() && !resLoc.getNamespace().equals(BlockUI.MOD_ID)) - { - throw new IllegalArgumentException("Missing texture: " + resLoc); - } - - return current; - } - final OutOfJarTexture outOfJarTexture = new OutOfJarTexture(outOfJarResLoc); - textureManager.register(outOfJarResLoc, outOfJarTexture); // this causes texture to load - - if (outOfJarTexture.redirectToSprite) - { - textureManager.register(outOfJarResLoc, new SpriteTexture(outOfJarResLoc)); - } + textureManager.registerAndLoad(outOfJarResLoc, outOfJarTexture); // this causes texture to load - // do recursive resolution - cant overflow because manager is aware of path now - return assertLoaded(outOfJarResLoc, textureManager, resourceManager); + return outOfJarTexture; } } diff --git a/src/main/java/com/ldtteam/blockui/util/texture/ResolvedWidgetSprites.java b/src/main/java/com/ldtteam/blockui/util/texture/ResolvedWidgetSprites.java new file mode 100644 index 00000000..8f61d865 --- /dev/null +++ b/src/main/java/com/ldtteam/blockui/util/texture/ResolvedWidgetSprites.java @@ -0,0 +1,79 @@ +package com.ldtteam.blockui.util.texture; + +import com.ldtteam.blockui.UiRenderMacros.ResolvedBlit; +import com.ldtteam.blockui.util.color.ColourQuartet4f; +import com.ldtteam.blockui.util.color.IColour; +import net.minecraft.client.gui.components.WidgetSprites; +import net.minecraft.resources.Identifier; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +/** + * Just as {@link WidgetSprites} but resolved + */ +public record ResolvedWidgetSprites(ResolvedBlit enabled, + ResolvedBlit disabled, + ResolvedBlit enabledFocused, + ResolvedBlit disabledFocused) +{ + //public static IColour FOCUSED_MODULATOR = new ColourQuartet4f(1.1f, 1.1f, 1.1f, 1.0f); + // TODO: cannot do more than byte max (ie. 255), need shader and buffer support to use this + public static IColour FOCUSED_MODULATOR = new ColourQuartet4f(1.0f, 1.0f, 1.0f, 1.0f); + public static IColour NORMAL_MODULATOR = new ColourQuartet4f(10 / 11.0f, 10 / 11.0f, 10 / 11.0f, 1.0f); + public static IColour DISABLED_MODULATOR = new ColourQuartet4f(0.5f, 0.5f, 0.5f, 1.0f); + + /** + * @return resolve given sprites using given resolver + */ + public static ResolvedWidgetSprites fromUnresolved(final WidgetSprites widgetSprites, + final Function resolver) + { + final Map resolved = new HashMap<>(); + final ResolvedBlit defaultEnabledBlit = resolver.apply(Objects.requireNonNull(widgetSprites.enabled(), "Forgot to put null check somewhere?")); + resolved.put(null, defaultEnabledBlit); + resolved.put(widgetSprites.enabled(), defaultEnabledBlit); + + return new ResolvedWidgetSprites(defaultEnabledBlit, + resolved.computeIfAbsent(widgetSprites.disabled(), resolver), + resolved.computeIfAbsent(widgetSprites.enabledFocused(), resolver), + resolved.computeIfAbsent(widgetSprites.disabledFocused(), resolver)); + } + + /** + * @param isEnabled whether element is interactive + * @param isFocused whether element is hovered/focused + * @return correct blit and also applies shader color + */ + public ResolvedBlit getAndPrepare(final boolean isEnabled, final boolean isFocused) + { + if (isEnabled) + { + if (isFocused) + { + return ifSameBlitModulateColor(enabled, enabledFocused, FOCUSED_MODULATOR); + } + else + { + return enabled.withColorModulation(NORMAL_MODULATOR); + } + } + else + { + if (isFocused) + { + return ifSameBlitModulateColor(enabled, disabledFocused, DISABLED_MODULATOR); + } + else + { + return ifSameBlitModulateColor(enabled, disabled, DISABLED_MODULATOR); + } + } + } + + private static ResolvedBlit ifSameBlitModulateColor(final ResolvedBlit test, final ResolvedBlit compared, final IColour modulator) + { + return compared == test ? compared.withColorModulation(modulator) : compared; + } +} diff --git a/src/main/java/com/ldtteam/blockui/views/BOWindow.java b/src/main/java/com/ldtteam/blockui/views/BOWindow.java index 04b20877..4673a041 100644 --- a/src/main/java/com/ldtteam/blockui/views/BOWindow.java +++ b/src/main/java/com/ldtteam/blockui/views/BOWindow.java @@ -7,18 +7,15 @@ import com.ldtteam.blockui.Parsers; import com.mojang.blaze3d.platform.Window; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.input.KeyEvent; import net.minecraft.resources.Identifier; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; -import org.lwjgl.glfw.GLFW; import java.util.function.ToDoubleBiFunction; /** * Blockout window, high level root pane. */ -@OnlyIn(Dist.CLIENT) public class BOWindow extends View { /** @@ -46,6 +43,11 @@ public class BOWindow extends View */ protected boolean lightbox = false; + /** + * Defines if the window should have blurred background. + */ + protected boolean blurBackground = false; + /** * Render using size or attemp to scale to fullscreen. */ @@ -56,21 +58,23 @@ public class BOWindow extends View /** * Create a window from an xml file. * - * @param resource ResourceLocation to get file from. + * @param resource Identifier to get file from. */ public BOWindow(final Identifier resource) { - this(); - this.xmlResourceLocation = resource; - Loader.createFromXMLFile(resource, this); + this(resource, true); } /** * Make default sized window. */ - public BOWindow() + public BOWindow(final Identifier resource, final boolean shouldloadXml) { - this(DEFAULT_WIDTH, DEFAULT_HEIGHT); + this(resource, DEFAULT_WIDTH, DEFAULT_HEIGHT); + if (shouldloadXml) + { + Loader.createFromXMLFile(resource, this); + } } /** @@ -79,9 +83,10 @@ public BOWindow() * @param w Width of the window, in pixels. * @param h Height of the window, in pixels. */ - public BOWindow(final int w, final int h) + public BOWindow(final Identifier resource, final int w, final int h) { super(); + xmlResourceLocation = resource; width = w; height = h; @@ -104,6 +109,7 @@ public void loadParams(final PaneParams params) }); lightbox = params.getBoolean("lightbox", lightbox); + blurBackground = params.getBoolean("blurBackground", blurBackground); windowPausesGame = params.getBoolean("pause", windowPausesGame); windowRenderType = params.getEnum("type", WindowRenderType.class, windowRenderType); } @@ -111,11 +117,16 @@ public void loadParams(final PaneParams params) @Override public void drawSelf(final BOGuiGraphics ms, final double mx, final double my) { - debugging = Screen.hasShiftDown() && Screen.hasAltDown() && Screen.hasControlDown(); + debugging = mc.hasShiftDown() && mc.hasAltDown() && mc.hasControlDown(); super.drawSelf(ms, mx, my); } + public void setLightbox(final boolean lightbox) + { + this.lightbox = lightbox; + } + /** * Return {@code true} if the 'lightbox' (default dark background) should be displayed. * @@ -126,6 +137,21 @@ public boolean hasLightbox() return lightbox; } + public void setBlurBackground(final boolean blurBackground) + { + this.blurBackground = blurBackground; + } + + public boolean hasBlurredBackground() + { + return blurBackground; + } + + public void setWindowPausesGame(final boolean windowPausesGame) + { + this.windowPausesGame = windowPausesGame; + } + /** * Return {@code true} if the game should be paused when the Window is displayed. * @@ -157,7 +183,7 @@ public Identifier getXmlResourceLocation() */ public void open() { - mc.submit(() -> mc.setScreen(screen)); + mc.submit(() -> mc.gui.setScreen(screen)); } /** @@ -165,7 +191,7 @@ public void open() */ public void openAsLayer() { - mc.submit(() -> mc.pushGuiLayer(screen)); + mc.submit(() -> mc.gui.setScreen(screen)); } /** @@ -192,25 +218,41 @@ public boolean onMouseReleased(final double mx, final double my) return false; } + /** + * Characted input handler. Directs text to focused Pane. + *

+ * It is advised not to override this method. + * + * @return {@code true} if the key was handled by a Pane. + */ + @Override + public boolean onCharactedEvent(final CharacterEvent event) + { + if (getFocus() != null && getFocus().onCharactedEvent(event)) + { + return true; + } + + return false; + } + /** * Key input handler. Directs keystrokes to focused Pane, or to onUnhandledKeyTyped() if no * Pane handles the keystroke. *

* It is advised not to override this method. * - * @param ch Character of key pressed. - * @param key Keycode of key pressed. * @return {@code true} if the key was handled by a Pane. */ @Override - public boolean onKeyTyped(final String ch, final int key) + public boolean onKeyEvent(final KeyEvent event) { - if (getFocus() != null && getFocus().onKeyTyped(ch, key)) + if (getFocus() != null && getFocus().onKeyEvent(event)) { return true; } - return onUnhandledKeyTyped(ch, key); + return onUnhandledKeyTyped(event); } /** @@ -218,12 +260,10 @@ public boolean onKeyTyped(final String ch, final int key) *

* Override this to handle key input at the Window level. * - * @param ch Character of key pressed. - * @param key Keycode of key pressed. */ - public boolean onUnhandledKeyTyped(final int ch, final int key) + public boolean onUnhandledKeyTyped(final KeyEvent event) { - if (key == GLFW.GLFW_KEY_ESCAPE) + if (event.isEscape()) { if (getFocus() != null) { @@ -243,7 +283,7 @@ public boolean onUnhandledKeyTyped(final int ch, final int key) */ public void close() { - Minecraft.getInstance().popGuiLayer(); + Minecraft.getInstance().gui.setScreen(null); } /** diff --git a/src/main/java/com/ldtteam/blockui/views/Box.java b/src/main/java/com/ldtteam/blockui/views/Box.java index e623a889..983d9e11 100644 --- a/src/main/java/com/ldtteam/blockui/views/Box.java +++ b/src/main/java/com/ldtteam/blockui/views/Box.java @@ -54,7 +54,7 @@ public void setLineWidth(final int lineWidth) { @Override public void drawSelf(final BOGuiGraphics ms, final double mx, final double my) { - drawLineRect(ms.pose(), x, y, width, height, color, lineWidth); + drawLineRect(ms, x, y, width, height, color, lineWidth); super.drawSelf(ms, mx, my); } diff --git a/src/main/java/com/ldtteam/blockui/views/OverlayView.java b/src/main/java/com/ldtteam/blockui/views/OverlayView.java index 996080f8..53b1016f 100644 --- a/src/main/java/com/ldtteam/blockui/views/OverlayView.java +++ b/src/main/java/com/ldtteam/blockui/views/OverlayView.java @@ -1,7 +1,7 @@ package com.ldtteam.blockui.views; import com.ldtteam.blockui.PaneParams; -import org.lwjgl.glfw.GLFW; +import net.minecraft.client.input.KeyEvent; /** * An OverlayView is a full screen View which is displayed on top of the window. @@ -60,19 +60,17 @@ public boolean rightClick(final double mx, final double my) * Called when a key is pressed. * hide the view when ESC is pressed. * - * @param ch the character. - * @param key the key. * @return false at all times - do nothing. */ @Override - public boolean onKeyTyped(final String ch, final int key) + public boolean onKeyEvent(final KeyEvent event) { - if (isVisible() && key == GLFW.GLFW_KEY_ESCAPE) + if (isVisible() && event.isEscape()) { setVisible(false); return true; } - return super.onKeyTyped(ch, key); + return super.onKeyEvent(event); } } diff --git a/src/main/java/com/ldtteam/blockui/views/ScrollingContainer.java b/src/main/java/com/ldtteam/blockui/views/ScrollingContainer.java index 713b592d..289884ed 100644 --- a/src/main/java/com/ldtteam/blockui/views/ScrollingContainer.java +++ b/src/main/java/com/ldtteam/blockui/views/ScrollingContainer.java @@ -69,8 +69,8 @@ public int getMaxScrollY() @Override public void drawSelf(final BOGuiGraphics target, final double mx, final double my) { - final Matrix3x2fStack ms = target.guiGraphics().pose(); - target.guiGraphics().enableScissor(x,y, x+width, y+height); + final Matrix3x2fStack ms = target.pose(); + scissorsStart(target); // Translate the scroll ms.pushMatrix(); @@ -78,17 +78,17 @@ public void drawSelf(final BOGuiGraphics target, final double mx, final double m super.drawSelf(target, mx, my + scrollY); ms.popMatrix(); - target.guiGraphics().disableScissor(); + scissorsEnd(target); } @Override public void drawSelfLast(final BOGuiGraphics target, final double mx, final double my) { - final Matrix3x2fStack ms = target.guiGraphics().pose(); + final Matrix3x2fStack ms = target.pose(); // Translate the scroll ms.pushMatrix(); - ms.translate(0.0F, (float) -scrollY); + ms.translate(0.0f, (float) -scrollY); super.drawSelfLast(target, mx, my + scrollY); ms.popMatrix(); } diff --git a/src/main/java/com/ldtteam/blockui/views/ScrollingView.java b/src/main/java/com/ldtteam/blockui/views/ScrollingView.java index 622cb284..9b231d7a 100644 --- a/src/main/java/com/ldtteam/blockui/views/ScrollingView.java +++ b/src/main/java/com/ldtteam/blockui/views/ScrollingView.java @@ -76,7 +76,7 @@ public void setSize(final int w, final int h) @Override public boolean scrollInput(final double horizontalWheel, final double verticalWheel, final double mx, final double my) { - return setScrollY(getScrollY() - verticalWheel * BOGuiGraphics.getAltSpeedFactor()); + return setScrollY(getScrollY() - verticalWheel * BOGuiGraphics.getAltSpeedFactor(mc)); } public ScrollingContainer getContainer() diff --git a/src/main/java/com/ldtteam/blockui/views/View.java b/src/main/java/com/ldtteam/blockui/views/View.java index dadb7e62..68405949 100644 --- a/src/main/java/com/ldtteam/blockui/views/View.java +++ b/src/main/java/com/ldtteam/blockui/views/View.java @@ -4,9 +4,8 @@ import com.ldtteam.blockui.controls.Tooltip; import com.ldtteam.blockui.util.records.Pos2i.ImmutablePos2i; import com.ldtteam.blockui.util.records.Pos2i.MutablePos2i; -import com.mojang.blaze3d.vertex.PoseStack; -import org.jetbrains.annotations.Nullable; import org.joml.Matrix3x2fStack; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -70,7 +69,7 @@ public void parseChildren(final PaneParams params) @Override public void drawSelf(final BOGuiGraphics target, final double mx, final double my) { - final Matrix3x2fStack ms = target.guiGraphics().pose(); + final Matrix3x2fStack ms = target.pose(); // Translate the drawing origin to our x,y. ms.pushMatrix(); @@ -111,7 +110,7 @@ public void drawHidden() @Override public void drawSelfLast(final BOGuiGraphics target, final double mx, final double my) { - final Matrix3x2fStack ms = target.guiGraphics().pose(); + final Matrix3x2fStack ms = target.pose(); // Translate the drawing origin to our x,y. ms.pushMatrix(); @@ -373,9 +372,9 @@ public void removeChild(final Pane child) } @Override - public boolean onMouseDrag(final double x, final double y, final double deltaX, final double deltaY) + public boolean onMouseDrag(final double x, final double y, final int speed, final double deltaX, final double deltaY) { - return mousePointableEventHandler(x, y, (child, mxChild, myChild) -> child.onMouseDrag(mxChild, myChild, deltaX, deltaY), null); + return mousePointableEventHandler(x, y, (child, mxChild, myChild) -> child.onMouseDrag(mxChild, myChild, speed, deltaX, deltaY), null); } /** diff --git a/src/main/java/com/ldtteam/blockui/views/ZoomDragView.java b/src/main/java/com/ldtteam/blockui/views/ZoomDragView.java index 9816a9d5..6016ea79 100644 --- a/src/main/java/com/ldtteam/blockui/views/ZoomDragView.java +++ b/src/main/java/com/ldtteam/blockui/views/ZoomDragView.java @@ -5,7 +5,9 @@ import com.ldtteam.blockui.Pane; import com.ldtteam.blockui.PaneParams; import com.ldtteam.blockui.controls.AbstractTextElement; -import com.mojang.blaze3d.vertex.PoseStack; +import com.ldtteam.blockui.util.cursor.Cursor; +import com.mojang.blaze3d.platform.cursor.CursorType; +import org.joml.Matrix3x2fStack; import net.minecraft.util.Mth; /** @@ -51,7 +53,7 @@ public ZoomDragView(final PaneParams params) zoomEnabled = params.getBoolean("zoomenabled", zoomEnabled); minScale = params.getDouble("minscale", minScale); maxScale = params.getDouble("maxscale", maxScale); - + this.cursor = this.cursor == Cursor.DEFAULT ? Cursor.RESIZE : this.cursor; } @@ -63,9 +65,9 @@ protected boolean childIsVisible(final Pane child) } @Override - public Cursor getCursor() + public CursorType getCursor() { - Cursor superCursor = super.getCursor(); + CursorType superCursor = super.getCursor(); // if not default if (superCursor != Cursor.RESIZE) @@ -172,29 +174,29 @@ public double getMaxScrollX() return Math.max(0, (double) contentWidth * scale - getWidth()); } - protected void abstractDrawSelfPre(final PoseStack ms, final double mx, final double my) + protected void abstractDrawSelfPre(final Matrix3x2fStack ms, final double mx, final double my) { } - protected void abstractDrawSelfPost(final PoseStack ms, final double mx, final double my) + protected void abstractDrawSelfPost(final Matrix3x2fStack ms, final double mx, final double my) { } @Override public void drawSelf(final BOGuiGraphics target, final double mx, final double my) { - final PoseStack ms = target.pose(); + final Matrix3x2fStack ms = target.pose(); - scissorsStart(ms, contentWidth, contentHeight); + scissorsStart(target); - ms.pushPose(); - ms.translate(-scrollX, -scrollY, 0.0d); - ms.translate((1 - scale) * x, (1 - scale) * y, 0.0d); - ms.scale((float) scale, (float) scale, 1.0f); + ms.pushMatrix(); + ms.translate((float) -scrollX, (float) -scrollY); + ms.translate((float) (1 - scale) * x, (float) (1 - scale) * y); + ms.scale((float) scale, (float) scale); abstractDrawSelfPre(ms, mx, my); super.drawSelf(target, calcRelativeX(mx), calcRelativeY(my)); abstractDrawSelfPost(ms, mx, my); - ms.popPose(); + ms.popMatrix(); scissorsEnd(target); } @@ -202,16 +204,16 @@ public void drawSelf(final BOGuiGraphics target, final double mx, final double m @Override public void drawSelfLast(final BOGuiGraphics target, final double mx, final double my) { - final PoseStack ms = target.pose(); + final Matrix3x2fStack ms = target.pose(); - scissorsStart(ms, contentWidth, contentHeight); + scissorsStart(target); - ms.pushPose(); - ms.translate(-scrollX, -scrollY, 0.0d); - ms.translate((1 - scale) * x, (1 - scale) * y, 0.0d); - ms.scale((float) scale, (float) scale, 1.0f); + ms.pushMatrix(); + ms.translate((float) -scrollX, (float) -scrollY); + ms.translate((float) (1 - scale) * x, (float) (1 - scale) * y); + ms.scale((float) scale, (float) scale); super.drawSelfLast(target, calcRelativeX(mx), calcRelativeY(my)); - ms.popPose(); + ms.popMatrix(); scissorsEnd(target); } @@ -227,13 +229,13 @@ public void setScrollX(final double offset) } @Override - public boolean onMouseDrag(final double startX, final double startY, final double x, final double y) + public boolean onMouseDrag(final double startX, final double startY, final int speed, final double x, final double y) { - final boolean childResult = super.onMouseDrag(startX, startY, calcRelativeX(x), calcRelativeY(y)); + final boolean childResult = super.onMouseDrag(startX, startY, speed, calcRelativeX(x), calcRelativeY(y)); if (!childResult && dragEnabled) { - setScrollX(scrollX - x * dragFactor * BOGuiGraphics.getAltSpeedFactor()); - setScrollY(scrollY - y * dragFactor * BOGuiGraphics.getAltSpeedFactor()); + setScrollX(scrollX - x * dragFactor * BOGuiGraphics.getAltSpeedFactor(mc)); + setScrollY(scrollY - y * dragFactor * BOGuiGraphics.getAltSpeedFactor(mc)); return true; } return childResult; @@ -249,7 +251,7 @@ public boolean scrollInput(final double horizontalWheel, final double verticalWh final double childY = my - y; final double oldX = (childX + scrollX) / scale; final double oldY = (childY + scrollY) / scale; - final double zoomFactor = this.zoomFactor * BOGuiGraphics.getAltSpeedFactor(); + final double zoomFactor = this.zoomFactor * BOGuiGraphics.getAltSpeedFactor(mc); scale = verticalWheel < 0 ? scale / zoomFactor : scale * zoomFactor; // try to round if around whole number (cuz of text texture) diff --git a/src/main/java/com/ldtteam/common/codec/Codecs.java b/src/main/java/com/ldtteam/common/codec/Codecs.java index d3d8c315..7c03866a 100644 --- a/src/main/java/com/ldtteam/common/codec/Codecs.java +++ b/src/main/java/com/ldtteam/common/codec/Codecs.java @@ -6,7 +6,7 @@ import com.mojang.serialization.DynamicOps; import com.mojang.serialization.codecs.RecordCodecBuilder; import io.netty.buffer.ByteBuf; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.nbt.NbtOps; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; diff --git a/src/main/java/com/ldtteam/common/codec/XmlOps.java b/src/main/java/com/ldtteam/common/codec/XmlOps.java new file mode 100644 index 00000000..40cf6572 --- /dev/null +++ b/src/main/java/com/ldtteam/common/codec/XmlOps.java @@ -0,0 +1,1232 @@ +package com.ldtteam.common.codec; + +import com.ldtteam.common.codec.XmlValue.XmlElement; +import com.ldtteam.common.codec.XmlValue.XmlNull; +import com.ldtteam.common.codec.XmlValue.XmlText; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.ListBuilder; +import com.mojang.serialization.MapLike; +import com.mojang.serialization.RecordBuilder; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import javax.annotation.Nullable; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.StringWriter; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Base64; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.UnaryOperator; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +/** + * {@link DynamicOps} implementation for XML, using {@link XmlValue} as the in-memory representation. + * + * Encoding Model + * + *

Maps/records become {@link XmlElement} instances. Encoding dispatches per-value: + * + *

    + *
  • Simple values (primitives, all-text lists) → XML attributes on the element + *
  • Complex values (nested maps, typed lists) → child elements; the map key becomes the child's tag name + *
  • Typed lists (dispatch polymorphism) → wrapped in a single child element whose tag is the key, with each item as a sub-child + *
+ * + * Reserved Keys + * + *
    + *
  • {@code "type"} — encoded as the element's tag name (not an attribute). Used by dispatch codecs + *
  • {@code "text"} — encoded as inline text content (an {@link XmlValue.XmlText} child), not an attribute. Enables {@code Hello} style output + *
  • {@code "children"} — list items are inlined as repeated {@code } child elements rather than wrapped in a container. Used for polymorphic child lists + *
+ * + * Primitive Lists + * + *

{@code IntStream} and {@code LongStream} are encoded as compact bracketed strings: {@code [1, 2, 3]}. These are stored as + * attribute values. On read, any attribute matching {@code [...]} is parsed as a list. Plain string values that happen to match this + * pattern are escaped with a {@code \} prefix on write. + * + * List/Scalar Ambiguity + * + *

A single child element with a given tag is indistinguishable from a one-element list in the XML structure. The codec layer + * resolves this via type knowledge (calling {@code getList()} vs {@code getMap()}). The generic {@code entries()} stream treats single + * children as scalar values. + * + * Thread Safety + * + *

This class is stateless and thread-safe. XML factory instances are cached per-thread via {@link ThreadLocal}. + * + * @see XmlValue + * @see JsonOps + */ +public class XmlOps implements DynamicOps +{ + public static final XmlOps INSTANCE = new XmlOps(); + + private static final ThreadLocal DOCUMENT_BUILDER_FACTORY = ThreadLocal.withInitial(() -> { + try + { + final DocumentBuilderFactory f = DocumentBuilderFactory.newInstance(); + f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + return f; + } + catch (final ParserConfigurationException e) + { + throw new RuntimeException(e); + } + }); + + private static final ThreadLocal TRANSFORMER_FACTORY = ThreadLocal.withInitial(() -> { + final TransformerFactory f = TransformerFactory.newInstance(); + f.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + f.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + return f; + }); + + private XmlOps() + {} + + // ========== Core ========== + + @Override + public XmlValue empty() + { + return XmlNull.NULL; + } + + @Override + public XmlValue emptyMap() + { + return new XmlElement(XmlElement.DEFAULT_TAG); + } + + @Override + public XmlValue emptyList() + { + return new XmlElement(XmlElement.LIST_TAG); + } + + // ========== Primitives ========== + + @Override + public XmlValue createString(final String value) + { + return new XmlText(value); + } + + @Override + public XmlValue createNumeric(final Number i) + { + return new XmlText(i.toString()); + } + + @Override + public XmlValue createBoolean(final boolean value) + { + return new XmlText(String.valueOf(value)); + } + + @Override + public DataResult getStringValue(final XmlValue input) + { + return input instanceof final XmlText t ? DataResult.success(t.value()) : DataResult.error(() -> "Not a string: " + input); + } + + @Override + public DataResult getNumberValue(final XmlValue input) + { + return input instanceof final XmlText t ? parseNumber(t.value()) : DataResult.error(() -> "Not a number: " + input); + } + + @Override + public DataResult getBooleanValue(final XmlValue input) + { + if (input instanceof final XmlText t) + { + if ("true".equals(t.value())) + { + return DataResult.success(true); + } + if ("false".equals(t.value())) + { + return DataResult.success(false); + } + } + return DataResult.error(() -> "Not a boolean: " + input); + } + + // ========== Map Operations ========== + + @Override + public XmlValue createMap(final Stream> map) + { + final List> entries = map.toList(); + String tagName = XmlElement.DEFAULT_TAG; + final LinkedHashMap attrs = new LinkedHashMap<>(); + final List children = new ArrayList<>(); + + for (final Pair entry : entries) + { + final String key = getStringOrNull(entry.getFirst()); + if (key == null) + { + continue; + } + final XmlValue value = entry.getSecond(); + + if (XmlValue.TYPE_KEY.equals(key)) + { + final String v = getStringOrNull(value); + if (v != null && !v.isEmpty()) + { + tagName = v; + } + } + else if (XmlValue.TEXT_KEY.equals(key)) + { + final String v = getStringOrNull(value); + if (v != null) + { + children.add(0, new XmlText(v)); + } + } + else + { + appendEntry(attrs, children, key, value); + } + } + return new XmlElement(tagName, attrs, children); + } + + @Override + public DataResult>> getMapValues(final XmlValue input) + { + return input instanceof final XmlElement e ? DataResult.success(buildMapEntries(e)) : + DataResult.error(() -> "Not an element: " + input); + } + + @Override + public DataResult>> getMapEntries(final XmlValue input) + { + return input instanceof final XmlElement e ? + DataResult.success(c -> buildMapEntries(e).forEach(p -> c.accept(p.getFirst(), p.getSecond()))) : + DataResult.error(() -> "Not an element: " + input); + } + + @Override + public DataResult> getMap(final XmlValue input) + { + if (!(input instanceof final XmlElement element)) + { + return DataResult.error(() -> "Not an element: " + input); + } + return DataResult.success(new MapLike<>() + { + @Nullable + @Override + public XmlValue get(final XmlValue key) + { + final String k = getStringOrNull(key); + return k != null ? get(k) : null; + } + + @Nullable + @Override + public XmlValue get(final String key) + { + if (XmlValue.TYPE_KEY.equals(key)) + { + return !XmlElement.DEFAULT_TAG.equals(element.tag()) ? new XmlText(element.tag()) : null; + } + if (XmlValue.TEXT_KEY.equals(key)) + { + final String tc = element.getTextContent(); + return tc != null ? new XmlText(tc) : null; + } + final String attrValue = element.getAttribute(key); + if (attrValue != null) + { + return isBracketedList(attrValue) ? parseBracketedList(attrValue) : new XmlText(unescapeAttrValue(attrValue)); + } + + final List matching = element.getChildrenByTag(key); + if (matching.isEmpty()) + { + // Backward-compat fallback: if requesting "children" and no child has that tag, + // collect ALL element children as a list (legacy inline-typed format). + if (XmlValue.CHILDREN_KEY.equals(key) && !element.children().isEmpty()) + { + final List all = element.children().stream().filter(c -> c instanceof XmlElement).toList(); + if (!all.isEmpty()) + { + return new XmlElement(XmlElement.LIST_TAG, Map.of(), all); + } + } + return null; + } + if (matching.size() == 1) + { + return unwrapChildAsValue(matching.get(0)); + } + return new XmlElement(XmlElement.LIST_TAG, + Map.of(), + matching.stream().map(XmlOps::unwrapChildAsValue).map(v -> (XmlValue) v).toList()); + } + + @Override + public Stream> entries() + { + return buildMapEntries(element); + } + + @Override + public String toString() + { + return "MapLike[" + element + "]"; + } + }); + } + + @Override + public DataResult mergeToMap(final XmlValue map, final XmlValue key, final XmlValue value) + { + if (!(map instanceof XmlElement) && !(map instanceof XmlNull)) + { + return DataResult.error(() -> "mergeToMap called with not a map: " + map, map); + } + final String keyStr = getStringOrNull(key); + if (keyStr == null) + { + return DataResult.error(() -> "key is not a string: " + key, map); + } + + XmlElement result = map instanceof final XmlElement e ? e : new XmlElement(XmlElement.DEFAULT_TAG); + + if (XmlValue.TYPE_KEY.equals(keyStr)) + { + final String v = getStringOrNull(value); + if (v != null && !v.isEmpty()) + { + result = result.withTag(v); + } + return DataResult.success(result); + } + if (XmlValue.TEXT_KEY.equals(keyStr)) + { + final String v = getStringOrNull(value); + if (v != null) + { + result = result.withoutTextContent().withTextContent(v); + } + return DataResult.success(result); + } + + if (isSimpleValue(value) && !isValidXmlName(keyStr)) + { + return DataResult.error(() -> "Invalid XML attribute name: " + keyStr, result); + } + + result = removeEntryFrom(result, keyStr); + return DataResult.success(appendToElement(result, keyStr, value)); + } + + @Override + public DataResult mergeToMap(final XmlValue map, final MapLike values) + { + if (!(map instanceof XmlElement) && !(map instanceof XmlNull)) + { + return DataResult.error(() -> "mergeToMap called with not a map: " + map, map); + } + + XmlElement result = map instanceof final XmlElement e ? e : new XmlElement(XmlElement.DEFAULT_TAG); + final List missed = new ArrayList<>(); + + for (final Pair entry : values.entries().toList()) + { + final String keyStr = getStringOrNull(entry.getFirst()); + if (keyStr == null) + { + missed.add(entry.getFirst()); + continue; + } + if (XmlValue.TYPE_KEY.equals(keyStr)) + { + continue; + } + if (XmlValue.TEXT_KEY.equals(keyStr)) + { + final String v = getStringOrNull(entry.getSecond()); + if (v != null) + { + result = result.withoutTextContent().withTextContent(v); + } + continue; + } + result = appendToElement(removeEntryFrom(result, keyStr), keyStr, entry.getSecond()); + } + + if (!missed.isEmpty()) + { + final XmlElement r = result; + return DataResult.error(() -> "some keys are not strings: " + missed, r); + } + final XmlValue typeNode = values.get(XmlValue.TYPE_KEY); + if (typeNode != null) + { + final String v = getStringOrNull(typeNode); + if (v != null && !v.isEmpty()) + { + result = result.withTag(v); + } + } + return DataResult.success(result); + } + + @Override + public XmlValue remove(final XmlValue input, final String key) + { + if (!(input instanceof final XmlElement element)) + { + return input; + } + if (XmlValue.TYPE_KEY.equals(key)) + { + return element.withTag(XmlElement.DEFAULT_TAG); + } + if (XmlValue.TEXT_KEY.equals(key)) + { + return element.withoutTextContent(); + } + return removeEntryFrom(element, key); + } + + // ========== List Operations ========== + + @Override + public XmlValue createList(final Stream input) + { + return new XmlElement(XmlElement.LIST_TAG, Map.of(), input.toList()); + } + + @Override + public DataResult> getStream(final XmlValue input) + { + if (input instanceof final XmlElement e && e.isList()) + { + return DataResult.success(e.children().stream()); + } + if (input instanceof final XmlText t && isBracketedList(t.value())) + { + return DataResult.success(parseBracketedItems(t.value()).stream().map(XmlText::new)); + } + if (input instanceof final XmlElement e) + { + if (e.attributes().isEmpty() && !e.children().isEmpty()) + { + return DataResult.success(e.children().stream()); + } + return DataResult.success(Stream.of(input)); + } + return DataResult.error(() -> "Not a list: " + input); + } + + @Override + public DataResult>> getList(final XmlValue input) + { + if (input instanceof final XmlElement e && e.isList()) + { + return DataResult.success(e.children()::forEach); + } + if (input instanceof final XmlText t && isBracketedList(t.value())) + { + final List items = parseBracketedItems(t.value()); + return DataResult.success(c -> items.forEach(i -> c.accept(new XmlText(i)))); + } + if (input instanceof final XmlElement e) + { + if (e.attributes().isEmpty() && !e.children().isEmpty()) + { + return DataResult.success(e.children()::forEach); + } + return DataResult.success(c -> c.accept(input)); + } + return DataResult.error(() -> "Not a list: " + input); + } + + @Override + public DataResult mergeToList(final XmlValue list, final XmlValue value) + { + if (!(list instanceof XmlElement) && !(list instanceof XmlNull)) + { + return DataResult.error(() -> "mergeToList called with not a list: " + list, list); + } + return DataResult.success( + list instanceof final XmlElement e ? e.withChild(value) : new XmlElement(XmlElement.LIST_TAG, Map.of(), List.of(value))); + } + + @Override + public DataResult mergeToList(final XmlValue list, final List values) + { + if (!(list instanceof XmlElement) && !(list instanceof XmlNull)) + { + return DataResult.error(() -> "mergeToList called with not a list: " + list, list); + } + if (values.isEmpty()) + { + return DataResult.success(list instanceof XmlNull ? emptyList() : list); + } + return DataResult.success( + list instanceof final XmlElement e ? e.withChildren(values) : new XmlElement(XmlElement.LIST_TAG, Map.of(), values)); + } + + // ========== Specialized Streams ========== + + @Override + public DataResult getByteBuffer(final XmlValue input) + { + if (!(input instanceof final XmlText t)) + { + return DataResult.error(() -> "Not a byte buffer: " + input); + } + try + { + return DataResult.success(ByteBuffer.wrap(Base64.getDecoder().decode(t.value()))); + } + catch (final IllegalArgumentException e) + { + return DataResult.error(() -> "Not valid Base64: " + t.value()); + } + } + + @Override + public XmlValue createByteList(final ByteBuffer input) + { + final byte[] bytes = new byte[input.remaining()]; + input.duplicate().get(bytes); + return new XmlText(Base64.getEncoder().encodeToString(bytes)); + } + + @Override + public DataResult getIntStream(final XmlValue input) + { + if (!(input instanceof final XmlText t) || !isBracketedList(t.value())) + { + return DataResult.error(() -> "Not an int list: " + input); + } + try + { + return DataResult.success(parseBracketedItems(t.value()).stream().mapToInt(Integer::parseInt)); + } + catch (final NumberFormatException e) + { + return DataResult.error(() -> "Not an int list: " + input); + } + } + + @Override + public XmlValue createIntList(final IntStream input) + { + return new XmlText(formatBracketedList(input.mapToObj(Integer::toString))); + } + + @Override + public DataResult getLongStream(final XmlValue input) + { + if (!(input instanceof final XmlText t) || !isBracketedList(t.value())) + { + return DataResult.error(() -> "Not a long list: " + input); + } + try + { + return DataResult.success(parseBracketedItems(t.value()).stream().mapToLong(Long::parseLong)); + } + catch (final NumberFormatException e) + { + return DataResult.error(() -> "Not a long list: " + input); + } + } + + @Override + public XmlValue createLongList(final LongStream input) + { + return new XmlText(formatBracketedList(input.mapToObj(Long::toString))); + } + + // ========== Conversion ========== + + @Override + public U convertTo(final DynamicOps outOps, final XmlValue input) + { + if (input instanceof XmlNull) + { + return outOps.empty(); + } + if (input instanceof final XmlText t) + { + if ("true".equals(t.value())) + { + return outOps.createBoolean(true); + } + if ("false".equals(t.value())) + { + return outOps.createBoolean(false); + } + final DataResult num = parseNumber(t.value()); + if (num.isSuccess()) + { + return outOps.createNumeric(num.getOrThrow(IllegalStateException::new)); + } + return outOps.createString(t.value()); + } + if (input instanceof final XmlElement e) + { + return e.isList() ? convertList(outOps, input) : convertMap(outOps, input); + } + return outOps.empty(); + } + + // ========== Builders ========== + + @Override + public ListBuilder listBuilder() + { + return new XmlListBuilder(); + } + + @Override + public RecordBuilder mapBuilder() + { + return new XmlRecordBuilder(); + } + + @Override + public String toString() + { + return "XML"; + } + + // ========== DOM Conversion ========== + + /** + * Converts an {@link XmlValue} tree to a W3C DOM {@link Node}. Useful for interop with standard XML APIs (XPath, XSLT, etc.). + */ + public static Node toNode(final XmlValue value) + { + try + { + return toNode(value, createSecureDocumentBuilder().newDocument()); + } + catch (final ParserConfigurationException e) + { + throw new RuntimeException("Failed to create XML Document", e); + } + } + + /** + * Converts a W3C DOM {@link Node} to an {@link XmlValue} tree. Whitespace-only text nodes are skipped. Unknown node types become + * {@link XmlNull}. + */ + public static XmlValue fromNode(final Node node) + { + if (node instanceof final Text t) + { + final String c = t.getTextContent(); + return (c == null || c.isEmpty()) ? XmlNull.NULL : new XmlText(c); + } + if (node instanceof final Element elem) + { + final LinkedHashMap attrs = new LinkedHashMap<>(); + final NamedNodeMap na = elem.getAttributes(); + for (int i = 0; i < na.getLength(); i++) + { + attrs.put(na.item(i).getNodeName(), na.item(i).getNodeValue()); + } + final List children = new ArrayList<>(); + final NodeList cn = elem.getChildNodes(); + for (int i = 0; i < cn.getLength(); i++) + { + final Node child = cn.item(i); + if (child instanceof Text && child.getTextContent().isBlank()) + { + continue; + } + children.add(fromNode(child)); + } + return new XmlElement(elem.getTagName(), attrs, children); + } + return XmlNull.NULL; + } + + /** + * Serializes an {@link XmlValue} to a pretty-printed XML string (no XML declaration). + * + * @return a {@link DataResult} containing the XML string, or an error if serialization fails + */ + public static DataResult toXmlString(final XmlValue value) + { + try + { + final Transformer tf = TRANSFORMER_FACTORY.get().newTransformer(); + tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + tf.setOutputProperty(OutputKeys.INDENT, "yes"); + tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + final StringWriter w = new StringWriter(); + tf.transform(new DOMSource(toNode(value)), new StreamResult(w)); + return DataResult.success(w.toString().trim()); + } + catch (final Exception e) + { + return DataResult.error(() -> "Failed to serialize XML: " + e.getMessage()); + } + } + + private static Node toNode(final XmlValue value, final Document doc) + { + if (value instanceof XmlNull) + { + return doc.createElement("null"); + } + if (value instanceof final XmlText t) + { + return doc.createTextNode(t.value()); + } + if (value instanceof final XmlElement element) + { + final Element e = doc.createElement(element.tag()); + element.attributes().forEach(e::setAttribute); + for (final XmlValue child : element.children()) + { + e.appendChild(toNode(child, doc)); + } + return e; + } + return doc.createTextNode(""); + } + + private static javax.xml.parsers.DocumentBuilder createSecureDocumentBuilder() throws ParserConfigurationException + { + return DOCUMENT_BUILDER_FACTORY.get().newDocumentBuilder(); + } + + // ========== Internal Helpers ========== + + @Nullable + private static String getStringOrNull(final XmlValue value) + { + return value instanceof final XmlText t ? t.value() : null; + } + + private static boolean isSimpleValue(final XmlValue value) + { + if (value instanceof XmlText) + { + return true; + } + return value instanceof final XmlElement e && e.isList() && e.children().stream().allMatch(c -> c instanceof XmlText); + } + + private static boolean hasTypedItems(final XmlElement list) + { + return list.children().stream().anyMatch(c -> c instanceof XmlElement e && !XmlElement.DEFAULT_TAG.equals(e.tag())); + } + + private static String getSimpleValueString(final XmlValue value) + { + if (value instanceof final XmlText t) + { + return escapeAttrValue(t.value()); + } + if (value instanceof final XmlElement e && e.isList()) + { + return "[" + + String.join(", ", e.children().stream().filter(c -> c instanceof XmlText).map(c -> ((XmlText) c).value()).toList()) + + "]"; + } + return ""; + } + + /** + * Core encoding dispatch: appends a key-value pair to attrs/children lists (used by createMap). + */ + private static void appendEntry(final Map attrs, + final List children, + final String key, + final XmlValue value) + { + if (isSimpleValue(value)) + { + attrs.put(key, getSimpleValueString(value)); + } + else if (value instanceof final XmlElement list && list.isList()) + { + if (hasTypedItems(list)) + { + if (XmlValue.CHILDREN_KEY.equals(key)) + { + for (final XmlValue item : list.children()) + { + if (item instanceof final XmlElement ce) + { + XmlElement child = ce.withTag(key); + if (!XmlElement.DEFAULT_TAG.equals(ce.tag())) + { + child = child.withAttribute(XmlValue.TYPE_KEY, ce.tag()); + } + children.add(child); + } + } + } + else + { + children.add(new XmlElement(key, Map.of(), list.children())); + } + } + else + { + for (final XmlValue item : list.children()) + { + if (item instanceof final XmlElement ce) + { + children.add(ce.withTag(key)); + } + else + { + children.add(new XmlElement(key, Map.of(XmlValue.TEXT_KEY, getSimpleValueString(item)))); + } + } + } + } + else if (value instanceof final XmlElement elem) + { + XmlElement child = elem.withTag(key); + if (!XmlElement.DEFAULT_TAG.equals(elem.tag())) + { + child = child.withAttribute(XmlValue.TYPE_KEY, elem.tag()); + } + children.add(child); + } + } + + /** + * Encodes a key-value pair into the element. + *

    + *
  • Simple values (text/number/bool) → stored as an attribute + *
  • Lists → items inlined as repeated child elements with the key as tag + *
  • Maps/elements → stored as a single child element with the key as tag
+ * Special handling for {@link XmlValue#CHILDREN_KEY}: typed items become {@code } children to preserve type + * info. + */ + private static XmlElement appendToElement(final XmlElement element, final String key, final XmlValue value) + { + if (isSimpleValue(value)) + { + return element.withAttribute(key, getSimpleValueString(value)); + } + + if (value instanceof final XmlElement list && list.isList()) + { + if (hasTypedItems(list)) + { + if (XmlValue.CHILDREN_KEY.equals(key)) + { + XmlElement result = element; + for (final XmlValue item : list.children()) + { + if (item instanceof final XmlElement ce) + { + XmlElement child = ce.withTag(key); + if (!XmlElement.DEFAULT_TAG.equals(ce.tag())) + { + child = child.withAttribute(XmlValue.TYPE_KEY, ce.tag()); + } + result = result.withChild(child); + } + } + return result; + } + return element.withChild(new XmlElement(key, Map.of(), list.children())); + } + final List newChildren = new ArrayList<>(element.children()); + for (final XmlValue item : list.children()) + { + if (item instanceof final XmlElement ce) + { + newChildren.add(ce.withTag(key)); + } + else + { + newChildren.add(new XmlElement(key, Map.of(XmlValue.TEXT_KEY, getSimpleValueString(item)))); + } + } + return new XmlElement(element.tag(), element.attributes(), newChildren); + } + + if (value instanceof final XmlElement elem) + { + XmlElement child = elem.withTag(key); + if (!XmlElement.DEFAULT_TAG.equals(elem.tag())) + { + child = child.withAttribute(XmlValue.TYPE_KEY, elem.tag()); + } + return element.withChild(child); + } + return element; + } + + /** + * Reconstructs map entries from an element's structure (inverse of encoding). Order: type key (from tag) → text key (from text + * content) → attributes → grouped children. + *

Note: a single child with a given tag is returned as a scalar value, not a one-element list. This is an inherent XML + * ambiguity; the codec layer resolves it via type knowledge. + */ + private Stream> buildMapEntries(final XmlElement element) + { + final List> entries = new ArrayList<>(); + if (!XmlElement.DEFAULT_TAG.equals(element.tag())) + { + entries.add(Pair.of(new XmlText(XmlValue.TYPE_KEY), new XmlText(element.tag()))); + } + + final String textContent = element.getTextContent(); + if (textContent != null) + { + entries.add(Pair.of(new XmlText(XmlValue.TEXT_KEY), new XmlText(textContent))); + } + + for (final var attr : element.attributes().entrySet()) + { + final String v = attr.getValue(); + entries.add( + Pair.of(new XmlText(attr.getKey()), isBracketedList(v) ? parseBracketedList(v) : new XmlText(unescapeAttrValue(v)))); + } + + final LinkedHashMap> groups = new LinkedHashMap<>(); + for (final XmlValue child : element.children()) + { + if (child instanceof final XmlElement ce) + { + groups.computeIfAbsent(ce.tag(), k -> new ArrayList<>()).add(ce); + } + } + + for (final var group : groups.entrySet()) + { + final List items = group.getValue(); + if (items.size() == 1) + { + entries.add(Pair.of(new XmlText(group.getKey()), unwrapChildAsValue(items.get(0)))); + } + else + { + entries.add(Pair.of(new XmlText(group.getKey()), + new XmlElement(XmlElement.LIST_TAG, Map.of(), items.stream().map(XmlOps::unwrapChildAsValue).toList()))); + } + } + return entries.stream(); + } + + private static XmlValue unwrapChildAsValue(final XmlElement child) + { + if (child.children().isEmpty() && child.attributes().size() == 1 && child.getAttribute(XmlValue.TEXT_KEY) != null) + { + return new XmlText(unescapeAttrValue(child.getAttribute(XmlValue.TEXT_KEY))); + } + final String typeAttr = child.getAttribute(XmlValue.TYPE_KEY); + final String tag = (typeAttr != null && !typeAttr.isEmpty()) ? typeAttr : XmlElement.DEFAULT_TAG; + return child.withTag(tag).withoutAttribute(XmlValue.TYPE_KEY); + } + + /** + * Removes the map entry identified by {@code key} from the element. Clears the key from both attributes and children. + * Short-circuits if neither exists. + */ + private static XmlElement removeEntryFrom(final XmlElement element, final String key) + { + if (element.getAttribute(key) == null && element.getChildrenByTag(key).isEmpty()) + { + return element; + } + return element.withoutAttribute(key).withoutChildrenByTag(key); + } + + /** + * Parses a numeric string, narrowing to the smallest type that can represent the value exactly. Order: byte → short → int → long → + * float → double. Codec consumers always call {@code .intValue()}, {@code .longValue()} etc., so the boxed type is safe. + */ + private static DataResult parseNumber(final String text) + { + try + { + final BigDecimal bd = new BigDecimal(text); + try + { + final long l = bd.longValueExact(); + if ((byte) l == l) + { + return DataResult.success((byte) l); + } + if ((short) l == l) + { + return DataResult.success((short) l); + } + if ((int) l == l) + { + return DataResult.success((int) l); + } + return DataResult.success(l); + } + catch (final ArithmeticException e) + { + final double d = bd.doubleValue(); + return DataResult.success((float) d == d ? (float) d : d); + } + } + catch (final NumberFormatException e) + { + return DataResult.error(() -> "Not a number: " + text); + } + } + + // ========== Bracketed List Format ========== + // Compact encoding for IntStream/LongStream: "[1, 2, 3]" stored as an attribute value. + // Only used for numeric primitive streams — never for arbitrary user strings. + // Strings that match the [...] pattern are escaped with a \ prefix on write (see escapeAttrValue). + + private static boolean isBracketedList(final String v) + { + return v.startsWith("[") && v.endsWith("]"); + } + + /** + * Escapes a plain string value before storing as an XML attribute. Prevents false-positive bracketed-list parsing on read. Values + * starting with '[' + ']' or '\' get a '\' prefix. + */ + private static String escapeAttrValue(final String value) + { + if (value.startsWith("\\") || isBracketedList(value)) + { + return "\\" + value; + } + return value; + } + + /** + * Reverses {@link #escapeAttrValue} — strips leading '\' if present. + */ + private static String unescapeAttrValue(final String value) + { + if (value.startsWith("\\")) + { + return value.substring(1); + } + return value; + } + + private static List parseBracketedItems(final String value) + { + final String inner = value.substring(1, value.length() - 1).trim(); + if (inner.isEmpty()) + { + return List.of(); + } + final String[] parts = inner.split(","); + final List items = new ArrayList<>(parts.length); + for (final String p : parts) + { + items.add(p.trim()); + } + return items; + } + + private static XmlValue parseBracketedList(final String value) + { + return new XmlElement(XmlElement.LIST_TAG, + Map.of(), + parseBracketedItems(value).stream().map(XmlText::new).map(v -> (XmlValue) v).toList()); + } + + private static String formatBracketedList(final Stream items) + { + return "[" + String.join(", ", items.toList()) + "]"; + } + + /** + * Validates that a string is usable as an XML element/attribute name. Rejects: empty, names starting with "xml" (reserved per + * spec), names with invalid characters. Allowed start chars: letter, underscore. Allowed continuation: letter, digit, underscore, + * hyphen, dot. Colons are rejected (no namespace support). + */ + private static boolean isValidXmlName(final String name) + { + if (name == null || name.isEmpty()) + { + return false; + } + if (name.length() >= 3 && name.regionMatches(true, 0, "xml", 0, 3)) + { + return false; + } + final char first = name.charAt(0); + if (!Character.isLetter(first) && first != '_') + { + return false; + } + for (int i = 1; i < name.length(); i++) + { + final char c = name.charAt(i); + if (!Character.isLetterOrDigit(c) && c != '_' && c != '-' && c != '.') + { + return false; + } + } + return true; + } + + // ========== List Builder ========== + + private class XmlListBuilder implements ListBuilder + { + private DataResult> builder = DataResult.success(new ArrayList<>(), Lifecycle.stable()); + + @Override + public DynamicOps ops() + { + return XmlOps.this; + } + + @Override + public ListBuilder add(final XmlValue value) + { + builder = builder.map(b -> { + b.add(value); + return b; + }); + return this; + } + + @Override + public ListBuilder add(final DataResult value) + { + builder = builder.apply2stable((b, v) -> { + b.add(v); + return b; + }, value); + return this; + } + + @Override + public ListBuilder withErrorsFrom(final DataResult result) + { + builder = builder.flatMap(r -> result.map(v -> r)); + return this; + } + + @Override + public ListBuilder mapError(final UnaryOperator onError) + { + builder = builder.mapError(onError); + return this; + } + + @Override + public DataResult build(final XmlValue prefix) + { + final DataResult result = builder.flatMap(b -> { + if (!(prefix instanceof XmlElement) && !(prefix instanceof XmlNull)) + { + return DataResult.error(() -> "Cannot append a list to not a list: " + prefix, prefix); + } + final List combined = new ArrayList<>(); + if (prefix instanceof final XmlElement e) + { + combined.addAll(e.children()); + } + combined.addAll(b); + return DataResult.success(new XmlElement(XmlElement.LIST_TAG, Map.of(), combined), Lifecycle.stable()); + }); + builder = DataResult.success(new ArrayList<>(), Lifecycle.stable()); + return result; + } + } + + // ========== Record Builder ========== + + private class XmlRecordBuilder extends RecordBuilder.AbstractStringBuilder + { + protected XmlRecordBuilder() + { + super(XmlOps.this); + } + + @Override + protected XmlElement initBuilder() + { + return new XmlElement(XmlElement.DEFAULT_TAG); + } + + @Override + protected XmlElement append(final String key, final XmlValue value, final XmlElement builder) + { + if (XmlValue.TYPE_KEY.equals(key)) + { + final String v = getStringOrNull(value); + if (v != null && !v.isEmpty()) + { + return builder.withTag(v); + } + return builder; + } + if (XmlValue.TEXT_KEY.equals(key)) + { + final String v = getStringOrNull(value); + if (v != null) + { + return builder.withoutTextContent().withTextContent(v); + } + return builder; + } + return appendToElement(builder, key, value); + } + + @Override + protected DataResult build(final XmlElement builder, final XmlValue prefix) + { + if (prefix == null || prefix instanceof XmlNull) + { + return DataResult.success(builder); + } + if (prefix instanceof final XmlElement pe) + { + final LinkedHashMap attrs = new LinkedHashMap<>(pe.attributes()); + attrs.putAll(builder.attributes()); + final List children = new ArrayList<>(pe.children()); + children.addAll(builder.children()); + final String tag = XmlElement.DEFAULT_TAG.equals(builder.tag()) ? pe.tag() : builder.tag(); + return DataResult.success(new XmlElement(tag, attrs, children)); + } + return DataResult.error(() -> "mergeToMap called with not a map: " + prefix, prefix); + } + } +} diff --git a/src/main/java/com/ldtteam/common/codec/XmlValue.java b/src/main/java/com/ldtteam/common/codec/XmlValue.java new file mode 100644 index 00000000..4568ff6f --- /dev/null +++ b/src/main/java/com/ldtteam/common/codec/XmlValue.java @@ -0,0 +1,251 @@ +package com.ldtteam.common.codec; + +import com.mojang.serialization.DynamicOps; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Sealed interface representing an XML value in memory, used by {@link XmlOps}. + *

Variants

+ *
    + *
  • {@link XmlNull} — singleton representing absence (maps to {@link DynamicOps#empty()}) + *
  • {@link XmlText} — a primitive value stored as text (string, number, or boolean) + *
  • {@link XmlElement} — a structured element with tag name, ordered attributes, and child nodes
+ *

Immutability

+ *

All instances are immutable. Modification methods (e.g., {@code withAttribute}, {@code withChild}) return new instances. This + * enables safe sharing across threads without synchronization. + *

Reserved Constants

+ *
    + *
  • {@link #TYPE_KEY} ({@code "type"}) — map key encoded as the element's tag name + *
  • {@link #TEXT_KEY} ({@code "text"}) — map key encoded as inline text content, not an attribute + *
  • {@link #CHILDREN_KEY} ({@code "children"}) — list key whose items are inlined as direct children
+ * + * @see XmlOps + */ +public sealed interface XmlValue permits XmlValue.XmlNull, XmlValue.XmlText, XmlValue.XmlElement +{ + XmlNull NULL = new XmlNull(); + + /** Map key that becomes the element's tag name instead of being stored as an attribute. */ + String TYPE_KEY = "type"; + + /** + * Map key whose value is stored as inline text content (an {@link XmlText} child node). Enables output like + * {@code Hello} instead of {@code }. + */ + String TEXT_KEY = "text"; + + /** + * Map key whose list value is encoded as repeated {@code } child elements. A backward-compat fallback in + * {@link XmlOps} collects all element children if no child has the tag "children". + */ + String CHILDREN_KEY = "children"; + + /** + * Represents an absent/empty value. + */ + record XmlNull() implements XmlValue + { + @Override + public String toString() + { + return "XmlNull"; + } + } + + /** + * Represents a primitive value stored as text (string, number, boolean). + */ + record XmlText(String value) implements XmlValue + { + public XmlText + { + Objects.requireNonNull(value, "value"); + } + + @Override + public String toString() + { + return "XmlText(\"" + value + "\")"; + } + } + + /** + * Represents a structured XML element with a tag name, attributes (ordered), and child elements. + *

When used as a map: attributes hold simple key-value pairs, children hold complex values (their tag name is the map key). + *

When used as a list: children are the list items (tag name is irrelevant to the list itself, the parent's key provides the + * tag for child items). + */ + record XmlElement(String tag, Map attributes, List children) implements XmlValue + { + /** Default tag name for map/record elements that have no explicit type. */ + public static final String DEFAULT_TAG = "object"; + + /** Tag name used for list elements. */ + public static final String LIST_TAG = "list"; + + public XmlElement + { + Objects.requireNonNull(tag, "tag"); + attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes)); + children = List.copyOf(children); + } + + public XmlElement(final String tag) + { + this(tag, Map.of(), List.of()); + } + + public XmlElement(final String tag, final Map attributes) + { + this(tag, attributes, List.of()); + } + + /** + * Returns an attribute value, or null if absent. + */ + @Nullable + public String getAttribute(final String name) + { + return attributes.get(name); + } + + /** + * Returns a new element with an additional/updated attribute. + */ + public XmlElement withAttribute(final String name, final String value) + { + final LinkedHashMap newAttrs = new LinkedHashMap<>(attributes); + newAttrs.put(name, value); + return new XmlElement(tag, newAttrs, children); + } + + /** + * Returns a new element without the specified attribute. + */ + public XmlElement withoutAttribute(final String name) + { + if (!attributes.containsKey(name)) + { + return this; + } + final LinkedHashMap newAttrs = new LinkedHashMap<>(attributes); + newAttrs.remove(name); + return new XmlElement(tag, newAttrs, children); + } + + /** + * Returns a new element with an additional child. + */ + public XmlElement withChild(final XmlValue child) + { + final List newChildren = new ArrayList<>(children); + newChildren.add(child); + return new XmlElement(tag, attributes, newChildren); + } + + /** + * Returns a new element with additional children. + */ + public XmlElement withChildren(final List additional) + { + final List newChildren = new ArrayList<>(children); + newChildren.addAll(additional); + return new XmlElement(tag, attributes, newChildren); + } + + /** + * Returns a new element without children that match the given tag. + */ + public XmlElement withoutChildrenByTag(final String childTag) + { + final List newChildren = + children.stream().filter(c -> !(c instanceof XmlElement e && e.tag.equals(childTag))).toList(); + return newChildren.size() == children.size() ? this : new XmlElement(tag, attributes, newChildren); + } + + /** + * Returns the concatenated text content (XmlText children), or null if none. + */ + @Nullable + public String getTextContent() + { + final StringBuilder sb = new StringBuilder(); + for (final XmlValue child : children) + { + if (child instanceof final XmlText t) + { + sb.append(t.value()); + } + } + return sb.isEmpty() ? null : sb.toString(); + } + + /** + * Returns a new element with its text content set (replaces any existing XmlText children). + */ + public XmlElement withTextContent(final String text) + { + final List newChildren = new ArrayList<>(children.stream().filter(c -> !(c instanceof XmlText)).toList()); + newChildren.add(0, new XmlText(text)); + return new XmlElement(tag, attributes, newChildren); + } + + /** + * Returns a new element with all XmlText children removed. + */ + public XmlElement withoutTextContent() + { + final List newChildren = children.stream().filter(c -> !(c instanceof XmlText)).toList(); + return new XmlElement(tag, attributes, newChildren); + } + + /** + * Returns a new element with a different tag name. + */ + public XmlElement withTag(final String newTag) + { + return new XmlElement(newTag, attributes, children); + } + + /** + * Returns true if this is a list element. + */ + public boolean isList() + { + return LIST_TAG.equals(tag); + } + + /** + * Returns child elements matching the given tag. + */ + public List getChildrenByTag(final String childTag) + { + return children.stream() + .filter(c -> c instanceof XmlElement e && e.tag.equals(childTag)) + .map(c -> (XmlElement) c) + .toList(); + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder("XmlElement(<").append(tag); + attributes.forEach((k, v) -> sb.append(' ').append(k).append("=\"").append(v).append('"')); + if (children.isEmpty()) + { + sb.append("/>"); + } + else + { + sb.append("> ").append(children.size()).append(" children)"); + } + return sb.toString(); + } + } +} diff --git a/src/main/java/com/ldtteam/common/config/AbstractConfiguration.java b/src/main/java/com/ldtteam/common/config/AbstractConfiguration.java index 636332e0..0a9aeee4 100644 --- a/src/main/java/com/ldtteam/common/config/AbstractConfiguration.java +++ b/src/main/java/com/ldtteam/common/config/AbstractConfiguration.java @@ -2,7 +2,6 @@ import com.ldtteam.common.language.LanguageHandler; import net.minecraft.server.TickTask; -import net.neoforged.fml.LogicalSide; import net.neoforged.fml.loading.FMLEnvironment; import net.neoforged.neoforge.common.ModConfigSpec.BooleanValue; import net.neoforged.neoforge.common.ModConfigSpec.Builder; @@ -12,7 +11,10 @@ import net.neoforged.neoforge.common.ModConfigSpec.IntValue; import net.neoforged.neoforge.common.ModConfigSpec.LongValue; import net.neoforged.neoforge.common.ModConfigSpec.RestartType; +import net.neoforged.neoforge.internal.NeoForgeProxy; +import net.neoforged.neoforge.server.ServerLifecycleHooks; import org.jetbrains.annotations.Nullable; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -280,8 +282,15 @@ synchronized void compareAndFireChangeEvent() if (!Objects.equals(newValue, lastValue)) { - LogicalSidedProvider.WORKQUEUE.get(FMLEnvironment.getDist().isClient() ? LogicalSide.CLIENT : LogicalSide.SERVER) - .tell(new TickTask(0, () -> listener.onChange(lastValue, newValue))); + final Runnable changeEvent = () -> listener.onChange(lastValue, newValue); + if (FMLEnvironment.getDist().isClient()) + { + NeoForgeProxy.INSTANCE.getClientExecutor().schedule(changeEvent); + } + else + { + ServerLifecycleHooks.getCurrentServer().schedule(new TickTask(0, changeEvent)); + } lastValue = newValue; } } diff --git a/src/main/java/com/ldtteam/common/config/Configurations.java b/src/main/java/com/ldtteam/common/config/Configurations.java index 28295cd5..517b36dc 100644 --- a/src/main/java/com/ldtteam/common/config/Configurations.java +++ b/src/main/java/com/ldtteam/common/config/Configurations.java @@ -76,7 +76,7 @@ public Configurations(final ModContainer modContainer, modBus.addListener(ModConfigEvent.Loading.class, event -> onConfigLoad(event.getConfig())); modBus.addListener(ModConfigEvent.Reloading.class, event -> onConfigReload(event.getConfig())); - if (FMLEnvironment.dist.isClient()) + if (FMLEnvironment.getDist().isClient()) { ClientConfigHelper.registerClient(modContainer); } @@ -88,7 +88,7 @@ private Pair createConfig(final final List configs) { // dont create client classes on server to avoid class loading issues - if (factory == null || (type == Type.CLIENT && !FMLEnvironment.dist.isClient())) + if (factory == null || (type == Type.CLIENT && !FMLEnvironment.getDist().isClient())) { return Pair.of(null, null); } diff --git a/src/main/java/com/ldtteam/common/fakelevel/FakeChunk.java b/src/main/java/com/ldtteam/common/fakelevel/FakeChunk.java index 57d958ac..2fec21a8 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/FakeChunk.java +++ b/src/main/java/com/ldtteam/common/fakelevel/FakeChunk.java @@ -1,12 +1,14 @@ package com.ldtteam.common.fakelevel; import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.shorts.ShortList; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData.BlockEntityTagOutput; import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeResolver; @@ -16,27 +18,25 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.Heightmap.Types; -import net.minecraft.world.level.levelgen.blending.BlendingData; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.ticks.BlackholeTickAccess; +import net.minecraft.world.ticks.LevelChunkTicks; import net.minecraft.world.ticks.TickContainerAccess; -import net.neoforged.neoforge.client.model.data.ModelData; +import net.neoforged.neoforge.model.data.ModelData; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BiPredicate; import java.util.function.Consumer; -import java.util.function.Predicate; import java.util.function.Supplier; /** @@ -53,19 +53,31 @@ public class FakeChunk extends LevelChunk { private final FakeLevel fakeLevel; - // section cache - int lastY; - LevelChunkSection lastSection = null; - - public FakeChunk(final FakeLevel worldIn, final int x, final int z) + public static FakeChunk create(final FakeLevel fakeLevel, final int x, final int z) { - super(worldIn, new ChunkPos(x, z)); - this.fakeLevel = worldIn; + // 26.1 porting notes - we need to create this ourselves or we will get vanilla sections + + final ChunkPos chunkPos = new ChunkPos(x, z); + final LevelChunkSection[] sections = new LevelChunkSection[fakeLevel.getSectionsCount()]; + for (int i = 0; i < sections.length; i++) + { + sections[i] = new FakeLevelChunkSection(fakeLevel, i, chunkPos); + } + + final FakeChunk chunk = new FakeChunk(fakeLevel, new ChunkPos(x, z), sections); // set itself to cache fakeLevel.lastX = x; fakeLevel.lastZ = z; - fakeLevel.lastChunk = this; + fakeLevel.lastChunk = chunk; + + return chunk; + } + + private FakeChunk(final FakeLevel fakeLevel, final ChunkPos pos, final LevelChunkSection[] sections) + { + super(fakeLevel, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, sections, null, null); + this.fakeLevel = fakeLevel; } // ======================================== @@ -160,6 +172,12 @@ public boolean isLightCorrect() return true; } + @Override + public boolean canBeSerialized() + { + return false; + } + // ======================================== // ========== HEIGHTMAP RELATED =========== // ======================================== @@ -189,53 +207,6 @@ public boolean hasPrimedHeightmap(Types p_187659_) return false; } - // ======================================== - // =========== SECTION RELATED ============ - // ======================================== - - @Override - public void findBlocks(Predicate filter, - BiPredicate fineFilter, - BiConsumer sink) - { - for (final BlockPos mutablePos : BlockPos.betweenClosed(chunkPos.getBlockX(0), - fakeLevel.levelSource.getMinBuildHeight(), - chunkPos.getBlockZ(0), - Math.min(chunkPos.getBlockX(15), fakeLevel.levelSource.getMaxX() - 1), - fakeLevel.levelSource.getMaxBuildHeight() - 1, - Math.min(chunkPos.getBlockZ(15), fakeLevel.levelSource.getMaxZ() - 1))) - { - final BlockState blockState = getBlockState(mutablePos); - if (fineFilter.test(blockState, mutablePos)) - { - sink.accept(mutablePos, blockState); - } - } - } - - @Override - public boolean isYSpaceEmpty(int p_62075_, int p_62076_) - { - return false; - } - - @Override - public LevelChunkSection[] getSections() - { - // don't cache them - return new LevelChunkSection[0]; - } - - @Override - public LevelChunkSection getSection(int yIdx) - { - if (lastY == yIdx && lastSection != null) - { - return lastSection; - } - return new FakeLevelChunkSection(this, yIdx); - } - // ======================================== // ============= NOOP METHODS ============= // ======================================== @@ -261,7 +232,7 @@ public TickContainerAccess getFluidTicks() } @Override - public void postProcessGeneration() + public void postProcessGeneration(ServerLevel level) { // Noop } @@ -285,7 +256,7 @@ public void replaceBiomes(FriendlyByteBuf p_275574_) } @Override - public void replaceWithPacketData(FriendlyByteBuf p_187972_, CompoundTag p_187973_, Consumer p_187974_) + public void replaceWithPacketData(FriendlyByteBuf p_187972_, Map p_187973_, Consumer p_187974_) { // Noop } @@ -298,7 +269,7 @@ public void setBlockEntity(BlockEntity p_156374_) @Override @javax.annotation.Nullable - public BlockState setBlockState(BlockPos p_62865_, BlockState p_62866_, boolean p_62867_) + public BlockState setBlockState(BlockPos p_62865_, BlockState p_62866_, @Block.UpdateFlags int p_62867_) { // Noop return null; @@ -316,12 +287,6 @@ public void unpackTicks(long p_187986_) // Noop } - @Override - public void addPackedPostProcess(short p_62092_, int p_62093_) - { - // Noop - } - @Override public void addReferenceForStructure(Structure p_223007_, long p_223008_) { @@ -355,37 +320,43 @@ public void setAllStarts(Map p_62090_) } @Override - public void setBlendingData(BlendingData p_187646_) + public void setBlockEntityNbt(CompoundTag p_62091_) { // Noop } @Override - public void setBlockEntityNbt(CompoundTag p_62091_) + public void setLightCorrect(boolean p_62100_) { // Noop } @Override - public void setLightCorrect(boolean p_62100_) + public void setStartForStructure(Structure p_223010_, StructureStart p_223011_) { // Noop } @Override - public void setStartForStructure(Structure p_223010_, StructureStart p_223011_) + public void setHeightmap(Types p_62083_, long[] p_62084_) { // Noop } @Override - public void setUnsaved(boolean p_62094_) + public void markUnsaved() { // Noop } @Override - public void setHeightmap(Types p_62083_, long[] p_62084_) + public void setUnsavedListener(UnsavedListener unsavedListener) + { + // Noop + } + + @Override + public void addPackedPostProcess(ShortList packedOffsets, int sectionIndex) { // Noop } @@ -400,245 +371,233 @@ public Level getLevel() { return super.getLevel(); } - + @Override public void addEntity(Entity p_62826_) { super.addEntity(p_62826_); } - + @Override public void clearAllBlockEntities() { super.clearAllBlockEntities(); } - + @Override @javax.annotation.Nullable public BlockEntity getBlockEntity(BlockPos p_62912_) { return super.getBlockEntity(p_62912_); } - + @Override public GameEventListenerRegistry getListenerRegistry(int p_251193_) { return super.getListenerRegistry(p_251193_); } - + @Override - public TicksToSave getTicksForSerialization() + public PackedTicks getTicksForSerialization(long currentTick) { - return super.getTicksForSerialization(); + return super.getTicksForSerialization(currentTick); } - + @Override public boolean isEmpty() { return super.isEmpty(); } - + @Override public void registerTickContainerInLevel(ServerLevel p_187959_) { super.registerTickContainerInLevel(p_187959_); } - + @Override public void runPostLoad() { super.runPostLoad(); } - + @Override public void setLoaded(boolean p_62914_) { super.setLoaded(p_62914_); } - + @Override public void unregisterTickContainerFromLevel(ServerLevel p_187980_) { super.unregisterTickContainerFromLevel(p_187980_); } - + @Override public BiomeGenerationSettings carverBiome(Supplier p_223015_) { return super.carverBiome(p_223015_); } - + @Override public void findBlocks(Predicate p_285343_, BiConsumer p_285030_) { super.findBlocks(p_285343_, p_285030_); } - - @Override - public void findBlocks(BiPredicate p_285343_, BiConsumer p_285030_) - { - super.findBlocks(p_285343_, p_285030_); - } - + @Override public Map getAllReferences() { return super.getAllReferences(); } - + @Override public Map getAllStarts() { return super.getAllStarts(); } - + @Override @javax.annotation.Nullable public BelowZeroRetrogen getBelowZeroRetrogen() { return super.getBelowZeroRetrogen(); } - + @Override @javax.annotation.Nullable public BlendingData getBlendingData() { return super.getBlendingData(); } - + @Override public int getHeight() { return super.getHeight(); } - + @Override public LevelHeightAccessor getHeightAccessorForGeneration() { return super.getHeightAccessorForGeneration(); } - + @Override public int getHighestFilledSectionIndex() { return super.getHighestFilledSectionIndex(); } - + @Override public ChunkStatus getHighestGeneratedStatus() { return super.getHighestGeneratedStatus(); } - + @Override public int getHighestSectionPosition() { return super.getHighestSectionPosition(); } - + @Override public long getInhabitedTime() { return super.getInhabitedTime(); } - + @Override - public int getMinBuildHeight() + public int getMinY() { - return super.getMinBuildHeight(); + return super.getMinY(); } - + @Override public NoiseChunk getOrCreateNoiseChunk(Function p_223013_) { return super.getOrCreateNoiseChunk(p_223013_); } - + @Override public ChunkPos getPos() { return super.getPos(); } - + @Override public ShortList[] getPostProcessing() { return super.getPostProcessing(); } - + @Override public LongSet getReferencesForStructure(Structure p_223017_) { return super.getReferencesForStructure(p_223017_); } - - @Override - public boolean isSectionEmpty(int p_350678_) - { - return super.isSectionEmpty(p_350678_); - } - + @Override public ChunkSkyLightSources getSkyLightSources() { return super.getSkyLightSources(); } - + @Override @javax.annotation.Nullable public StructureStart getStartForStructure(Structure p_223005_) { return super.getStartForStructure(p_223005_); } - + @Override public UpgradeData getUpgradeData() { return super.getUpgradeData(); } - + @Override public boolean hasAnyStructureReferences() { return super.hasAnyStructureReferences(); } - + @Override public void incrementInhabitedTime(long p_187633_) { super.incrementInhabitedTime(p_187633_); } - + @Override public void initializeLightSources() { super.initializeLightSources(); } - + @Override public boolean isOldNoiseGeneration() { return super.isOldNoiseGeneration(); } - + @Override public void markPosForPostprocessing(BlockPos p_62102_) { super.markPosForPostprocessing(p_62102_); } - + @Override public void setInhabitedTime(long p_62099_) { super.setInhabitedTime(p_62099_); } - + @Override public BlockHitResult clip(ClipContext p_45548_) { return super.clip(p_45548_); } - + @Override @javax.annotation.Nullable public BlockHitResult clipWithInteractionOverride(Vec3 p_45559_, @@ -649,192 +608,266 @@ public BlockHitResult clipWithInteractionOverride(Vec3 p_45559_, { return super.clipWithInteractionOverride(p_45559_, p_45560_, p_45561_, p_45562_, p_45563_); } - + @Override public Optional getBlockEntity(BlockPos p_151367_, BlockEntityType p_151368_) { return super.getBlockEntity(p_151367_, p_151368_); } - + @Override public double getBlockFloorHeight(BlockPos p_45574_) { return super.getBlockFloorHeight(p_45574_); } - + @Override public double getBlockFloorHeight(VoxelShape p_45565_, Supplier p_45566_) { return super.getBlockFloorHeight(p_45565_, p_45566_); } - + @Override public Stream getBlockStates(AABB p_45557_) { return super.getBlockStates(p_45557_); } - + @Override public int getLightEmission(BlockPos p_45572_) { return super.getLightEmission(p_45572_); } - - @Override - public int getMaxLightLevel() - { - return super.getMaxLightLevel(); - } - + @Override public BlockHitResult isBlockInLine(ClipBlockStateContext p_151354_) { return super.isBlockInLine(p_151354_); } - + @Override - public int getMaxBuildHeight() + public int getMaxY() { - return super.getMaxBuildHeight(); + return super.getMaxY(); } - + @Override - public int getMaxSection() + public int getMaxSectionY() { - return super.getMaxSection(); + return super.getMaxSectionY(); } - + @Override - public int getMinSection() + public int getMinSectionY() { - return super.getMinSection(); + return super.getMinSectionY(); } - + @Override public int getSectionIndex(int p_151565_) { return super.getSectionIndex(p_151565_); } - + @Override public int getSectionIndexFromSectionY(int p_151567_) { return super.getSectionIndexFromSectionY(p_151567_); } - + @Override public int getSectionYFromSectionIndex(int p_151569_) { return super.getSectionYFromSectionIndex(p_151569_); } - + @Override public int getSectionsCount() { return super.getSectionsCount(); } - + @Override public boolean isOutsideBuildHeight(BlockPos p_151571_) { return super.isOutsideBuildHeight(p_151571_); } - + @Override public boolean isOutsideBuildHeight(int p_151563_) { return super.isOutsideBuildHeight(p_151563_); } - + @Override public LevelChunkAuxiliaryLightManager getAuxLightManager(ChunkPos pos) { return super.getAuxLightManager(pos); } - + @Override @Nullable public AuxiliaryLightManager getAuxLightManager(BlockPos pos) { return super.getAuxLightManager(pos); } - + @Override public T getData(Supplier> type) { return super.getData(type); } - + @Override public boolean hasData(Supplier> type) { return super.hasData(type); } - + @Override public @Nullable T setData(Supplier> type, T data) { return super.setData(type, data); } - + @Override public T getData(AttachmentType type) { return super.getData(type); } - + @Override public boolean hasData(AttachmentType type) { return super.hasData(type); } - + @Override public T setData(AttachmentType type, T data) { return super.setData(type, data); } - + @Override public Optional getExistingData(AttachmentType type) { return super.getExistingData(type); } - + @Override public boolean hasAttachments() { return super.hasAttachments(); } - + @Override public T removeData(AttachmentType type) { return super.removeData(type); } - + @Override public Optional getExistingData(Supplier> type) { return super.getExistingData(type); } - + @Override public @Nullable T removeData(Supplier> type) { return super.removeData(type); } - + @Override - protected AsField getAttachmentHolder() + public AsField getAttachmentHolder() { return super.getAttachmentHolder(); } - + + @Override + public @org.jspecify.annotations.Nullable T getExistingDataOrNull(AttachmentType type) + { + return super.getExistingDataOrNull(type); + } + + @Override + public @org.jspecify.annotations.Nullable T getExistingDataOrNull(Supplier> type) + { + return super.getExistingDataOrNull(type); + } + + @Override + public void syncData(Supplier> type) + { + super.syncData(type); + } + @Override public CompoundTag getBlockEntityNbtForSaving(BlockPos p_62932_, Provider p_323699_) { return super.getBlockEntityNbtForSaving(p_62932_, p_323699_); } + + @Override + public LevelChunkSection getSection(int yIdx) + { + return super.getSection(yIdx); + } + + @Override + public void registerDebugValues(ServerLevel level, Registration registration) + { + super.registerDebugValues(level, registration); + } + + @Override + public void findBlocks(Predicate predicate, + BiPredicate fineFilter, + BiConsumer consumer) + { + super.findBlocks(predicate, fineFilter, consumer); + } + + @Override + public LevelChunkSection[] getSections() + { + return super.getSections(); + } + + @Override + public boolean isYSpaceEmpty(int yStartInclusive, int yEndInclusive) + { + return super.isYSpaceEmpty(yStartInclusive, yEndInclusive); + } + + @Override + public PathElement problemPath() + { + return super.problemPath(); + } + + @Override + public @org.jspecify.annotations.Nullable BlockState setBlockState(BlockPos pos, BlockState state) + { + return super.setBlockState(pos, state); + } + + @Override + public boolean tryMarkSaved() + { + return super.tryMarkSaved(); + } + + @Override + public boolean isInsideBuildHeight(int blockY) + { + return super.isInsideBuildHeight(blockY); + } + + @Override + public boolean isInsideBuildHeight(BlockPos pos) + { + return super.isInsideBuildHeight(pos); + } */ } diff --git a/src/main/java/com/ldtteam/common/fakelevel/FakeChunkSource.java b/src/main/java/com/ldtteam/common/fakelevel/FakeChunkSource.java index 656f07a1..60a952d8 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/FakeChunkSource.java +++ b/src/main/java/com/ldtteam/common/fakelevel/FakeChunkSource.java @@ -83,15 +83,15 @@ public boolean hasChunk(int p_62238_, int p_62239_) } @Override - public void setSpawnSettings(boolean p_62236_, boolean p_62237_) + public void setSpawnSettings(boolean p_62236_) { - super.setSpawnSettings(p_62236_, p_62237_); + super.setSpawnSettings(p_62236_); } @Override - public void updateChunkForced(ChunkPos p_62233_, boolean p_62234_) + public boolean updateChunkForced(ChunkPos p_62233_, boolean p_62234_) { - super.updateChunkForced(p_62233_, p_62234_); + return super.updateChunkForced(p_62233_, p_62234_); } @Override @@ -99,5 +99,17 @@ public void onLightUpdate(LightLayer p_63021_, SectionPos p_63022_) { super.onLightUpdate(p_63021_, p_63022_); } + + @Override + public LongSet getForceLoadedChunks() + { + return super.getForceLoadedChunks(); + } + + @Override + public void onSectionEmptinessChanged(int sectionX, int sectionY, int sectionZ, boolean empty) + { + super.onSectionEmptinessChanged(sectionX, sectionY, sectionZ, empty); + } */ } diff --git a/src/main/java/com/ldtteam/common/fakelevel/FakeLevel.java b/src/main/java/com/ldtteam/common/fakelevel/FakeLevel.java index c2495c85..5064654d 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/FakeLevel.java +++ b/src/main/java/com/ldtteam/common/fakelevel/FakeLevel.java @@ -14,7 +14,6 @@ import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; import net.minecraft.util.AbortableIterationConsumer.Continuation; -import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.util.random.WeightedList; import net.minecraft.world.TickRateManager; import net.minecraft.world.attribute.EnvironmentAttributeSystem; @@ -26,9 +25,7 @@ import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.item.alchemy.PotionBrewing; import net.minecraft.world.item.crafting.RecipeAccess; -import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.Explosion; import net.minecraft.world.level.ExplosionDamageCalculator; import net.minecraft.world.level.Level; import net.minecraft.world.level.LightLayer; @@ -44,6 +41,7 @@ import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkSource; import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.PalettedContainerFactory; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.entity.EntityTypeTest; @@ -54,9 +52,10 @@ import net.minecraft.world.level.lighting.LevelLightEngine; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.redstone.Orientation; import net.minecraft.world.level.saveddata.maps.MapId; import net.minecraft.world.level.saveddata.maps.MapItemSavedData; -import net.minecraft.world.level.storage.LevelData; +import net.minecraft.world.level.storage.LevelData.RespawnData; import net.minecraft.world.phys.Vec3; import net.minecraft.world.scores.Scoreboard; import net.minecraft.world.ticks.BlackholeTickAccess; @@ -64,15 +63,16 @@ import net.neoforged.neoforge.entity.PartEntity; import net.neoforged.neoforge.model.data.ModelData; import net.neoforged.neoforge.model.data.ModelDataManager; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; + import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.Supplier; /** * As much as general fake level. Features: @@ -104,9 +104,10 @@ public class FakeLevel extends Level protected final boolean overrideBeLevel; protected final FakeChunkSource chunkSource; - protected final FakeLevelLightEngine lightEngine; - protected final ModelDataManager modelDataManager; - protected FakeLevelEntityGetterAdapter levelEntityGetter = FakeLevelEntityGetterAdapter.EMPTY; + protected final FakeLevelLightEngine lightEngine; + protected final ModelDataManager modelDataManager; + protected FakeLevelEntityGetterAdapter levelEntityGetter = FakeLevelEntityGetterAdapter.EMPTY; + protected List> dragonParts = List.of(); // TODO: this is currently manually filled by class user - ideally if not filled yet this should get constructed from levelSource // manually protected Map blockEntities = Collections.emptyMap(); @@ -217,7 +218,7 @@ public BlockPos getWorldPos() /** * For better block entity handling in chunk methods. If set then {@link IFakeLevelBlockGetter#getBlockEntity(BlockPos) * levelSource.getBlockEntity(BlockPos)} is not used. Reset with empty collection - * + * * @param blockEntities all block entities, should be data equivalent to levelSource */ public void setBlockEntities(final Map blockEntities) @@ -231,6 +232,7 @@ public void setBlockEntities(final Map blockEntities) public void setEntities(final Collection entities) { levelEntityGetter = entities.isEmpty() ? FakeLevelEntityGetterAdapter.EMPTY : FakeLevelEntityGetterAdapter.ofEntities(entities); + dragonParts = entities.stream().filter(Entity::isMultipartEntity).map(Entity::getParts).flatMap(Arrays::stream).toList(); } // ======================================== @@ -297,13 +299,14 @@ public BlockState getBlockState(final BlockPos pos) } @Override - public ChunkAccess getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull) + public ChunkAccess getChunk(int x, int z, ChunkStatus requiredStatus, boolean loadOrGenerate) { + // loadOrGenerate effectively means non-null return value if (lastX == x && lastZ == z && lastChunk != null) { return lastChunk; } - return nonnull || hasChunk(x, z) ? new FakeChunk(this, x, z) : null; + return loadOrGenerate || hasChunk(x, z) ? FakeChunk.create(this, x, z) : null; } @Override @@ -311,9 +314,8 @@ public boolean hasChunk(int chunkX, int chunkZ) { final int posX = SectionPos.sectionToBlockCoord(chunkX); final int posZ = SectionPos.sectionToBlockCoord(chunkZ); - return levelSource.getMinX() <= posX && posX < levelSource.getMaxX() && - levelSource.getMinZ() <= posZ && - posZ < levelSource.getMaxZ(); + return levelSource.getMinX() <= posX && posX <= levelSource.getMaxX() && + levelSource.getMinZ() <= posZ && posZ <= levelSource.getMaxZ(); } @Override @@ -336,6 +338,12 @@ public int getSkyDarken() return lightProvider.forceOwnLightLevel() ? lightProvider.getSkyDarken() : realLevel().getSkyDarken(); } + @Override + public boolean isBrightOutside() + { + return !this.dimensionType().hasFixedTime() && this.getSkyDarken() < 4; + } + @Override public Scoreboard getScoreboard() { @@ -354,6 +362,12 @@ public int getHeight() return levelSource.getHeight(); } + @Override + public int getMinY() + { + return levelSource.getMinY(); + } + @Override public boolean isInWorldBounds(final BlockPos pos) { @@ -395,11 +409,11 @@ public List players() @Override public int getHeight(Types heightmapType, int x, int z) { - final MutableBlockPos pos = new MutableBlockPos(x, levelSource.getMinBuildHeight(), z); + final MutableBlockPos pos = new MutableBlockPos(x, levelSource.getMinY(), z); if (levelSource.isPosInside(pos)) { - for (int y = levelSource.getMaxY() - 1; y >= levelSource.getMinBuildHeight(); y--) + for (int y = levelSource.getMaxY(); y >= levelSource.getMinY(); y--) { pos.setY(y); if (heightmapType.isOpaque().test(levelSource.getBlockState(pos))) @@ -409,7 +423,7 @@ public int getHeight(Types heightmapType, int x, int z) } } - return levelSource.getMinBuildHeight(); + return levelSource.getMinY(); } @Override @@ -438,15 +452,21 @@ public LevelLightEngine getLightEngine() } @Override - public String gatherChunkSourceStats() + public RespawnData getRespawnData() { - return "Fake level for: " + levelSource; + return getLevelData().getRespawnData(); + } + + @Override + public Collection> dragonParts() + { + return dragonParts; } @Override - public float getShade(Direction p_104703_, boolean p_104704_) + public String gatherChunkSourceStats() { - return realLevel().getShade(p_104703_, p_104704_); + return "Fake level for: " + levelSource; } @Override @@ -461,6 +481,12 @@ public BiomeManager getBiomeManager() return realLevel().getBiomeManager(); } + @Override + public RecipeAccess recipeAccess() + { + return realLevel().recipeAccess(); + } + @Override public FeatureFlagSet enabledFeatures() { @@ -492,23 +518,57 @@ public PotionBrewing potionBrewing() } @Override - public RecipeAccess recipeAccess() + public ClockManager clockManager() { - return realLevel.recipeAccess(); + return realLevel().clockManager(); } @Override - public ClockManager clockManager() + public PalettedContainerFactory palettedContainerFactory() + { + return realLevel().palettedContainerFactory(); + } + + @Override + public int getClientLeafTintColor(BlockPos pos) + { + return realLevel().getClientLeafTintColor(pos); + } + + @Override + public EnvironmentAttributeSystem environmentAttributes() + { + return realLevel().environmentAttributes(); + } + + @Override + public FuelValues fuelValues() { - return realLevel.clockManager(); + return realLevel().fuelValues(); } // ======================================== // ======= NOOP UNSAFE NULL METHODS ======= // ======================================== - - + @Override + public void explode( + final @Nullable Entity source, + final @Nullable DamageSource damageSource, + final @Nullable ExplosionDamageCalculator damageCalculator, + final double x, + final double y, + final double z, + final float r, + final boolean fire, + final Level.ExplosionInteraction interactionType, + final ParticleOptions smallExplosionParticles, + final ParticleOptions largeExplosionParticles, + final WeightedList blockParticles, + final Holder explosionSound) + { + // Noop throw new UnsupportedOperationException("Structurize fake immutable level - no explosions possible!"); + } // ======================================== // ========== PERMANENT SETTINGS ========== @@ -571,6 +631,32 @@ public MapItemSavedData getMapData(MapId p_324234_) return null; } + @Override + public void playSeededSound(final @Nullable Entity except, + final Entity sourceEntity, + final Holder sound, + final SoundSource source, + final float volume, + final float pitch, + final long seed) + { + // Noop + } + + @Override + public void playSeededSound(final @Nullable Entity except, + final double x, + final double y, + final double z, + final Holder sound, + final SoundSource source, + final float volume, + final float pitch, + final long seed) + { + // Noop + } + @Override public void sendBlockUpdated(BlockPos p_46612_, BlockState p_46613_, BlockState p_46614_, int p_46615_) { @@ -597,6 +683,12 @@ public LevelTickAccess getFluidTicks() return BlackholeTickAccess.emptyLevelList(); } + @Override + public void levelEvent(@javax.annotation.Nullable Entity p_46771_, int p_46772_, BlockPos p_46773_, int p_46774_) + { + // Noop + } + // ======================================== // ============= NOOP METHODS ============= // ======================================== @@ -644,164 +736,108 @@ public void markAndNotifyBlock(BlockPos p_46605_, } @Override - public boolean removeBlock(BlockPos p_46623_, boolean p_46624_) - { - return false; - } - - @Override - public boolean setBlock(BlockPos p_46605_, BlockState p_46606_, int p_46607_, int p_46608_) + public boolean mayInteract(Entity p_46557_, BlockPos p_46558_) { // Noop return false; } @Override - public void setRainLevel(float p_46735_) - { - // Noop - } - - @Override - public void setThunderLevel(float p_46708_) + public void neighborShapeChanged(Direction p_220385_, + BlockPos p_220387_, + BlockPos p_220388_, + BlockState p_220386_, + int p_220389_, + int p_220390_) { // Noop } @Override - public boolean shouldTickBlocksAt(long p_186456_) + public boolean removeBlock(BlockPos p_46623_, boolean p_46624_) { - // Noop return false; } @Override - public boolean shouldTickDeath(Entity p_186458_) + public boolean setBlock(BlockPos p_46605_, BlockState p_46606_, int p_46607_, int p_46608_) { // Noop return false; } @Override - public void updateNeighborsAt(BlockPos p_46673_, Block p_46674_) + public void setRainLevel(float p_46735_) { // Noop } @Override - public void updateSkyBrightness() + public void setSpawnSettings(boolean p_46704_) { // Noop } @Override - public void invalidateCapabilities(BlockPos pos) + public void setThunderLevel(float p_46708_) { // Noop } @Override - public void invalidateCapabilities(ChunkPos pos) + public boolean shouldTickBlocksAt(long p_186456_) { // Noop + return false; } @Override - public void playSeededSound( - @org.jspecify.annotations.Nullable final Entity except, - final Entity sourceEntity, - final Holder sound, - final SoundSource source, - final float volume, - final float pitch, - final long seed) + public boolean shouldTickDeath(Entity p_186458_) { // Noop + return false; } @Override - public void playSeededSound( - @org.jspecify.annotations.Nullable final Entity except, - final double x, - final double y, - final double z, - final Holder sound, - final SoundSource source, - final float volume, - final float pitch, - final long seed) + public void tickBlockEntities() { // Noop } @Override - public void playSeededSound( - @org.jspecify.annotations.Nullable final Entity except, - final double x, - final double y, - final double z, - final SoundEvent sound, - final SoundSource source, - final float volume, - final float pitch, - final long seed) + public void updateNeighborsAt(BlockPos p_46673_, Block p_46674_) { // Noop } @Override - public void levelEvent(@org.jspecify.annotations.Nullable final Entity source, final int type, final BlockPos pos, final int data) + public void updateSkyBrightness() { // Noop } @Override - public void explode( - @org.jspecify.annotations.Nullable final Entity source, - @org.jspecify.annotations.Nullable final DamageSource damageSource, - @org.jspecify.annotations.Nullable final ExplosionDamageCalculator damageCalculator, - final double x, - final double y, - final double z, - final float r, - final boolean fire, - final ExplosionInteraction interactionType, - final ParticleOptions smallExplosionParticles, - final ParticleOptions largeExplosionParticles, - final WeightedList blockParticles, - final Holder explosionSound) + public void invalidateCapabilities(BlockPos pos) { // Noop } @Override - public Collection> dragonParts() - { - return List.of(); - } - - @Override - public LevelData.RespawnData getRespawnData() - { - return LevelData.RespawnData.DEFAULT; - } - - @Override - public void setRespawnData(final LevelData.RespawnData respawnData) + public void invalidateCapabilities(ChunkPos pos) { // Noop } @Override - public FuelValues fuelValues() + public void updateNeighborsAt(BlockPos pos, Block sourceBlock, @Nullable Orientation orientation) { - return realLevel.fuelValues(); + // Noop } @Override - public EnvironmentAttributeSystem environmentAttributes() + public void setRespawnData(RespawnData respawnData) { - return realLevel.environmentAttributes(); + // Noop } // ======================================== @@ -814,7 +850,7 @@ public void removeBlockEntity(BlockPos p_46748_) { super.removeBlockEntity(p_46748_); } - + @Override public void addAlwaysVisibleParticle(ParticleOptions p_46684_, double p_46685_, @@ -826,7 +862,7 @@ public void addAlwaysVisibleParticle(ParticleOptions p_46684_, { super.addAlwaysVisibleParticle(p_46684_, p_46685_, p_46686_, p_46687_, p_46688_, p_46689_, p_46690_); } - + @Override public void addAlwaysVisibleParticle(ParticleOptions p_46691_, boolean p_46692_, @@ -839,13 +875,13 @@ public void addAlwaysVisibleParticle(ParticleOptions p_46691_, { super.addAlwaysVisibleParticle(p_46691_, p_46692_, p_46693_, p_46694_, p_46695_, p_46696_, p_46697_, p_46698_); } - + @Override public void addDestroyBlockEffect(BlockPos p_151531_, BlockState p_151532_) { super.addDestroyBlockEffect(p_151531_, p_151532_); } - + @Override public void addParticle(ParticleOptions p_46631_, double p_46632_, @@ -857,38 +893,25 @@ public void addParticle(ParticleOptions p_46631_, { super.addParticle(p_46631_, p_46632_, p_46633_, p_46634_, p_46635_, p_46636_, p_46637_); } - - @Override - public void addParticle(ParticleOptions p_46638_, - boolean p_46639_, - double p_46640_, - double p_46641_, - double p_46642_, - double p_46643_, - double p_46644_, - double p_46645_) - { - super.addParticle(p_46638_, p_46639_, p_46640_, p_46641_, p_46642_, p_46643_, p_46644_, p_46645_); - } - + @Override public void blockEntityChanged(BlockPos p_151544_) { super.blockEntityChanged(p_151544_); } - + @Override public void broadcastDamageEvent(Entity p_270831_, DamageSource p_270361_) { super.broadcastDamageEvent(p_270831_, p_270361_); } - + @Override public void broadcastEntityEvent(Entity p_46509_, byte p_46510_) { super.broadcastEntityEvent(p_46509_, p_46510_); } - + @Override public void createFireworks(double p_46475_, double p_46476_, @@ -900,26 +923,20 @@ public void createFireworks(double p_46475_, { super.createFireworks(p_46475_, p_46476_, p_46477_, p_46478_, p_46479_, p_46480_, p_332050_); } - - @Override - public void disconnect() - { - super.disconnect(); - } - + @Override - public Explosion explode(@javax.annotation.Nullable Entity p_256599_, + public void explode(@javax.annotation.Nullable Entity p_256599_, double p_255914_, double p_255684_, double p_255843_, float p_256310_, ExplosionInteraction p_256178_) { - return super.explode(p_256599_, p_255914_, p_255684_, p_255843_, p_256310_, p_256178_); + super.explode(p_256599_, p_255914_, p_255684_, p_255843_, p_256310_, p_256178_); } - + @Override - public Explosion explode(@javax.annotation.Nullable Entity p_255682_, + public void explode(@javax.annotation.Nullable Entity p_255682_, double p_255803_, double p_256403_, double p_256538_, @@ -927,11 +944,11 @@ public Explosion explode(@javax.annotation.Nullable Entity p_255682_, boolean p_256634_, ExplosionInteraction p_256111_) { - return super.explode(p_255682_, p_255803_, p_256403_, p_256538_, p_255674_, p_256634_, p_256111_); + super.explode(p_255682_, p_255803_, p_256403_, p_256538_, p_255674_, p_256634_, p_256111_); } - + @Override - public Explosion explode(@javax.annotation.Nullable Entity p_255653_, + public void explode(@javax.annotation.Nullable Entity p_255653_, @javax.annotation.Nullable DamageSource p_256558_, @javax.annotation.Nullable ExplosionDamageCalculator p_255929_, Vec3 p_256001_, @@ -939,11 +956,11 @@ public Explosion explode(@javax.annotation.Nullable Entity p_255653_, boolean p_256099_, ExplosionInteraction p_256371_) { - return super.explode(p_255653_, p_256558_, p_255929_, p_256001_, p_255963_, p_256099_, p_256371_); + super.explode(p_255653_, p_256558_, p_255929_, p_256001_, p_255963_, p_256099_, p_256371_); } - + @Override - public Explosion explode(@javax.annotation.Nullable Entity p_256145_, + public void explode(@javax.annotation.Nullable Entity p_256145_, @javax.annotation.Nullable DamageSource p_256004_, @javax.annotation.Nullable ExplosionDamageCalculator p_255696_, double p_256208_, @@ -953,58 +970,46 @@ public Explosion explode(@javax.annotation.Nullable Entity p_256145_, boolean p_256098_, ExplosionInteraction p_256104_) { - return super.explode(p_256145_, p_256004_, p_255696_, p_256208_, p_256036_, p_255746_, p_256647_, p_256098_, p_256104_); + super.explode(p_256145_, p_256004_, p_255696_, p_256208_, p_256036_, p_255746_, p_256647_, p_256098_, p_256104_); } - + @Override public BlockPos getBlockRandomPos(int p_46497_, int p_46498_, int p_46499_, int p_46500_) { return super.getBlockRandomPos(p_46497_, p_46498_, p_46499_, p_46500_); } - + @Override public LevelChunk getChunk(int p_46727_, int p_46728_) { return super.getChunk(p_46727_, p_46728_); } - + @Override public LevelChunk getChunkAt(BlockPos p_46746_) { return super.getChunkAt(p_46746_); } - + @Override @javax.annotation.Nullable public BlockGetter getChunkForCollisions(int p_46711_, int p_46712_) { return super.getChunkForCollisions(p_46711_, p_46712_); } - - @Override - public DifficultyInstance getCurrentDifficultyAt(BlockPos p_46730_) - { - return super.getCurrentDifficultyAt(p_46730_); - } - - @Override - public long getDayTime() - { - return super.getDayTime(); - } - + @Override public List getEntities(@javax.annotation.Nullable Entity p_46536_, AABB p_46537_, Predicate p_46538_) { return super.getEntities(p_46536_, p_46537_, p_46538_); } - + @Override public List getEntities(EntityTypeTest p_151528_, AABB p_151529_, Predicate p_151530_) { return super.getEntities(p_151528_, p_151529_, p_151530_); } - + @Override public void getEntities(EntityTypeTest p_261899_, AABB p_261837_, @@ -1013,7 +1018,7 @@ public void getEntities(EntityTypeTest p_261899_, { super.getEntities(p_261899_, p_261837_, p_261519_, p_262046_); } - + @Override public void getEntities(EntityTypeTest p_261885_, AABB p_262086_, @@ -1023,152 +1028,116 @@ public void getEntities(EntityTypeTest p_261885_, { super.getEntities(p_261885_, p_262086_, p_261688_, p_262071_, p_261858_); } - - @Override - public GameRules getGameRules() - { - return super.getGameRules(); - } - + @Override public long getGameTime() { return super.getGameTime(); } - + @Override public LevelData getLevelData() { return super.getLevelData(); } - + @Override public double getMaxEntityRadius() { return super.getMaxEntityRadius(); } - + @Override public RandomSource getRandom() { return super.getRandom(); } - + @Override @javax.annotation.Nullable public MinecraftServer getServer() { return super.getServer(); } - - @Override - public float getSharedSpawnAngle() - { - return super.getSharedSpawnAngle(); - } - - @Override - public BlockPos getSharedSpawnPos() - { - return super.getSharedSpawnPos(); - } - - @Override - public float getSunAngle(float p_46491_) - { - return super.getSunAngle(p_46491_); - } - + @Override public void globalLevelEvent(int p_46665_, BlockPos p_46666_, int p_46667_) { super.globalLevelEvent(p_46665_, p_46666_, p_46667_); } - + @Override public void guardEntityTick(Consumer p_46654_, T p_46655_) { super.guardEntityTick(p_46654_, p_46655_); } - + @Override public double increaseMaxEntityRadius(double value) { return super.increaseMaxEntityRadius(value); } - + @Override public boolean isClientSide() { return super.isClientSide(); } - + @Override public boolean isFluidAtPosition(BlockPos p_151541_, Predicate p_151542_) { return super.isFluidAtPosition(p_151541_, p_151542_); } - - @Override - public boolean isNight() - { - return super.isNight(); - } - + @Override public boolean isRaining() { return super.isRaining(); } - + @Override public boolean isStateAtPosition(BlockPos p_46620_, Predicate p_46621_) { return super.isStateAtPosition(p_46620_, p_46621_); } - + @Override public boolean isThundering() { return super.isThundering(); } - + @Override public boolean loadedAndEntityCanStandOn(BlockPos p_46576_, Entity p_46577_) { return super.loadedAndEntityCanStandOn(p_46576_, p_46577_); } - + @Override public boolean loadedAndEntityCanStandOnFace(BlockPos p_46579_, Entity p_46580_, Direction p_46581_) { return super.loadedAndEntityCanStandOnFace(p_46579_, p_46580_, p_46581_); } - + @Override - public void neighborChanged(BlockPos p_46587_, Block p_46588_, BlockPos p_46589_) + public void neighborChanged(BlockPos p_46587_, Block p_46588_, Orientation p_46589_) { super.neighborChanged(p_46587_, p_46588_, p_46589_); } - + @Override - public void neighborChanged(BlockState p_220379_, BlockPos p_220380_, Block p_220381_, BlockPos p_220382_, boolean p_220383_) + public void neighborChanged(BlockState p_220379_, BlockPos p_220380_, Block p_220381_, Orientation p_220382_, boolean p_220383_) { super.neighborChanged(p_220379_, p_220380_, p_220381_, p_220382_, p_220383_); } - + @Override public long nextSubTickCount() { return super.nextSubTickCount(); } - - @Override - public void onBlockStateChange(BlockPos p_46609_, BlockState p_46610_, BlockState p_46611_) - { - super.onBlockStateChange(p_46609_, p_46610_, p_46611_); - } - + @Override public void playLocalSound(BlockPos p_250938_, SoundEvent p_252209_, @@ -1179,7 +1148,7 @@ public void playLocalSound(BlockPos p_250938_, { super.playLocalSound(p_250938_, p_252209_, p_249161_, p_249980_, p_250277_, p_250151_); } - + @Override public void playLocalSound(double p_46482_, double p_46483_, @@ -1192,9 +1161,9 @@ public void playLocalSound(double p_46482_, { super.playLocalSound(p_46482_, p_46483_, p_46484_, p_46485_, p_46486_, p_46487_, p_46488_, p_46489_); } - + @Override - public void playSeededSound(@javax.annotation.Nullable Player p_220363_, + public void playSeededSound(@javax.annotation.Nullable Entity p_220363_, double p_220364_, double p_220365_, double p_220366_, @@ -1206,7 +1175,7 @@ public void playSeededSound(@javax.annotation.Nullable Player p_220363_, { super.playSeededSound(p_220363_, p_220364_, p_220365_, p_220366_, p_220367_, p_220368_, p_220369_, p_220370_, p_220371_); } - + @Override public void playSound(@javax.annotation.Nullable Entity p_252137_, BlockPos p_251749_, @@ -1217,20 +1186,9 @@ public void playSound(@javax.annotation.Nullable Entity p_252137_, { super.playSound(p_252137_, p_251749_, p_248842_, p_251104_, p_249531_, p_250763_); } - - @Override - public void playSound(@javax.annotation.Nullable Player p_46560_, - BlockPos p_46561_, - SoundEvent p_46562_, - SoundSource p_46563_, - float p_46564_, - float p_46565_) - { - super.playSound(p_46560_, p_46561_, p_46562_, p_46563_, p_46564_, p_46565_); - } - + @Override - public void playSound(@javax.annotation.Nullable Player p_46551_, + public void playSound(@javax.annotation.Nullable Entity p_46551_, Entity p_46552_, SoundEvent p_46553_, SoundSource p_46554_, @@ -1239,9 +1197,9 @@ public void playSound(@javax.annotation.Nullable Player p_46551_, { super.playSound(p_46551_, p_46552_, p_46553_, p_46554_, p_46555_, p_46556_); } - + @Override - public void playSound(@javax.annotation.Nullable Player p_46543_, + public void playSound(@javax.annotation.Nullable Entity p_46543_, double p_46544_, double p_46545_, double p_46546_, @@ -1252,9 +1210,9 @@ public void playSound(@javax.annotation.Nullable Player p_46543_, { super.playSound(p_46543_, p_46544_, p_46545_, p_46546_, p_46547_, p_46548_, p_46549_, p_46550_); } - + @Override - public void playSound(Player p_347719_, + public void playSound(Entity p_347719_, double p_347460_, double p_347457_, double p_347558_, @@ -1265,242 +1223,170 @@ public void playSound(Player p_347719_, { super.playSound(p_347719_, p_347460_, p_347457_, p_347558_, p_347499_, p_347522_, p_347447_, p_347667_); } - - @Override - protected void prepareWeather() - { - super.prepareWeather(); - } - + @Override public void sendPacketToServer(Packet p_46657_) { super.sendPacketToServer(p_46657_); } - + @Override public boolean setBlock(BlockPos p_46601_, BlockState p_46602_, int p_46603_) { return super.setBlock(p_46601_, p_46602_, p_46603_); } - + @Override public boolean setBlockAndUpdate(BlockPos p_46598_, BlockState p_46599_) { return super.setBlockAndUpdate(p_46598_, p_46599_); } - + @Override public void setBlocksDirty(BlockPos p_46678_, BlockState p_46679_, BlockState p_46680_) { super.setBlocksDirty(p_46678_, p_46679_, p_46680_); } - + @Override public void setSkyFlashTime(int p_46709_) { super.setSkyFlashTime(p_46709_); } - + @Override public void setBlockEntity(BlockEntity p_151524_) { super.setBlockEntity(p_151524_); } - + @Override public boolean shouldTickBlocksAt(BlockPos p_220394_) { return super.shouldTickBlocksAt(p_220394_); } - + @Override - public void updateNeighborsAtExceptFromFacing(BlockPos p_46591_, Block p_46592_, Direction p_46593_) + public void updateNeighborsAtExceptFromFacing(BlockPos p_46591_, Block p_46592_, Direction p_46593_, @Nullable Orientation orientation) { - super.updateNeighborsAtExceptFromFacing(p_46591_, p_46592_, p_46593_); + super.updateNeighborsAtExceptFromFacing(p_46591_, p_46592_, p_46593_, orientation); } - + @Override - public void blockUpdated(BlockPos p_46781_, Block p_46782_) + public void gameEvent(Entity p_151549_, Holder p_316314_, Vec3 p_316613_) { - super.blockUpdated(p_46781_, p_46782_); + super.gameEvent(p_151549_, p_316314_, p_316613_); } - + @Override - public long dayTime() + public void gameEvent(Entity p_316772_, Holder p_316248_, BlockPos p_316282_) { - return super.dayTime(); + super.gameEvent(p_316772_, p_316248_, p_316282_); } - - @Override - public void gameEvent(Entity p_151549_, Holder p_316314_, Vec3 p_316613_) - { - super.gameEvent(p_151549_, p_316314_, p_316613_); - } - - @Override - public void gameEvent(Entity p_316772_, Holder p_316248_, BlockPos p_316282_) - { - super.gameEvent(p_316772_, p_316248_, p_316282_); - } - + @Override public void gameEvent(Holder p_316320_, BlockPos p_220409_, Context p_220410_) { super.gameEvent(p_316320_, p_220409_, p_220410_); } - + @Override public void gameEvent(ResourceKey p_316780_, BlockPos p_316509_, Context p_316524_) { super.gameEvent(p_316780_, p_316509_, p_316524_); } - + @Override public Difficulty getDifficulty() { return super.getDifficulty(); } - + @Override public void levelEvent(int p_46797_, BlockPos p_46798_, int p_46799_) { super.levelEvent(p_46797_, p_46798_, p_46799_); } - + @Override - public void playSound(@javax.annotation.Nullable Player p_251195_, BlockPos p_250192_, SoundEvent p_249887_, SoundSource p_250593_) + public void playSound(@javax.annotation.Nullable Entity p_251195_, BlockPos p_250192_, SoundEvent p_249887_, SoundSource p_250593_) { super.playSound(p_251195_, p_250192_, p_249887_, p_250593_); } - + @Override public void scheduleTick(BlockPos p_186461_, Block p_186462_, int p_186463_) { super.scheduleTick(p_186461_, p_186462_, p_186463_); } - + @Override public void scheduleTick(BlockPos p_186470_, Fluid p_186471_, int p_186472_) { super.scheduleTick(p_186470_, p_186471_, p_186472_); } - + @Override public void scheduleTick(BlockPos p_186465_, Block p_186466_, int p_186467_, TickPriority p_186468_) { super.scheduleTick(p_186465_, p_186466_, p_186467_, p_186468_); } - + @Override public void scheduleTick(BlockPos p_186474_, Fluid p_186475_, int p_186476_, TickPriority p_186477_) { super.scheduleTick(p_186474_, p_186475_, p_186476_, p_186477_); } - + @Override public Optional getBlockEntity(BlockPos p_151452_, BlockEntityType p_151453_) { return super.getBlockEntity(p_151452_, p_151453_); } - + @Override public List getEntityCollisions(@javax.annotation.Nullable Entity p_186447_, AABB p_186448_) { return super.getEntityCollisions(p_186447_, p_186448_); } - + @Override public BlockPos getHeightmapPos(Types p_45831_, BlockPos p_45832_) { return super.getHeightmapPos(p_45831_, p_45832_); } - + @Override public boolean isUnobstructed(@javax.annotation.Nullable Entity p_45828_, VoxelShape p_45829_) { return super.isUnobstructed(p_45828_, p_45829_); } - + @Override public List getEntities(@javax.annotation.Nullable Entity p_45934_, AABB p_45935_) { return super.getEntities(p_45934_, p_45935_); } - + @Override public List getEntitiesOfClass(Class p_45977_, AABB p_45978_) { return super.getEntitiesOfClass(p_45977_, p_45978_); } - + @Override public List getEntitiesOfClass(Class p_45979_, AABB p_45980_, Predicate p_45981_) { return super.getEntitiesOfClass(p_45979_, p_45980_, p_45981_); } - - @Override - public List getNearbyEntities(Class p_45972_, - TargetingConditions p_45973_, - LivingEntity p_45974_, - AABB p_45975_) - { - return super.getNearbyEntities(p_45972_, p_45973_, p_45974_, p_45975_); - } - - @Override - public List getNearbyPlayers(TargetingConditions p_45956_, LivingEntity p_45957_, AABB p_45958_) - { - return super.getNearbyPlayers(p_45956_, p_45957_, p_45958_); - } - - @Override - @javax.annotation.Nullable - public T getNearestEntity(List p_45983_, - TargetingConditions p_45984_, - @javax.annotation.Nullable LivingEntity p_45985_, - double p_45986_, - double p_45987_, - double p_45988_) - { - return super.getNearestEntity(p_45983_, p_45984_, p_45985_, p_45986_, p_45987_, p_45988_); - } - - @Override - @javax.annotation.Nullable - public T getNearestEntity(Class p_45964_, - TargetingConditions p_45965_, - @javax.annotation.Nullable LivingEntity p_45966_, - double p_45967_, - double p_45968_, - double p_45969_, - AABB p_45970_) - { - return super.getNearestEntity(p_45964_, p_45965_, p_45966_, p_45967_, p_45968_, p_45969_, p_45970_); - } - + @Override @javax.annotation.Nullable public Player getNearestPlayer(Entity p_45931_, double p_45932_) { return super.getNearestPlayer(p_45931_, p_45932_); } - - @Override - @javax.annotation.Nullable - public Player getNearestPlayer(TargetingConditions p_45947_, LivingEntity p_45948_) - { - return super.getNearestPlayer(p_45947_, p_45948_); - } - - @Override - @javax.annotation.Nullable - public Player getNearestPlayer(TargetingConditions p_45942_, double p_45943_, double p_45944_, double p_45945_) - { - return super.getNearestPlayer(p_45942_, p_45943_, p_45944_, p_45945_); - } - + @Override @javax.annotation.Nullable public Player getNearestPlayer(double p_45919_, @@ -1511,170 +1397,153 @@ public Player getNearestPlayer(double p_45919_, { return super.getNearestPlayer(p_45919_, p_45920_, p_45921_, p_45922_, p_45923_); } - + @Override @javax.annotation.Nullable public Player getNearestPlayer(double p_45925_, double p_45926_, double p_45927_, double p_45928_, boolean p_45929_) { return super.getNearestPlayer(p_45925_, p_45926_, p_45927_, p_45928_, p_45929_); } - - @Override - @javax.annotation.Nullable - public Player getNearestPlayer(TargetingConditions p_45950_, - LivingEntity p_45951_, - double p_45952_, - double p_45953_, - double p_45954_) - { - return super.getNearestPlayer(p_45950_, p_45951_, p_45952_, p_45953_, p_45954_); - } - + @Override @javax.annotation.Nullable public Player getPlayerByUUID(UUID p_46004_) { return super.getPlayerByUUID(p_46004_); } - + @Override public boolean hasNearbyAlivePlayer(double p_45915_, double p_45916_, double p_45917_, double p_45918_) { return super.hasNearbyAlivePlayer(p_45915_, p_45916_, p_45917_, p_45918_); } - + @Override public boolean canSeeSkyFromBelowWater(BlockPos p_46862_) { return super.canSeeSkyFromBelowWater(p_46862_); } - + @Override public boolean containsAnyLiquid(AABB p_46856_) { return super.containsAnyLiquid(p_46856_); } - + @Override public Stream getBlockStatesIfLoaded(AABB p_46848_) { return super.getBlockStatesIfLoaded(p_46848_); } - - @Override - public int getBlockTint(BlockPos p_46836_, ColorResolver p_46837_) - { - return super.getBlockTint(p_46836_, p_46837_); - } - + @Override public ChunkAccess getChunk(BlockPos p_46866_) { return super.getChunk(p_46866_); } - + @Override public ChunkAccess getChunk(int p_46820_, int p_46821_, ChunkStatus p_46822_) { return super.getChunk(p_46820_, p_46821_, p_46822_); } - + @Override public float getLightLevelDependentMagicValue(BlockPos p_220418_) { return super.getLightLevelDependentMagicValue(p_220418_); } - + @Override public int getMaxLocalRawBrightness(BlockPos p_46804_) { return super.getMaxLocalRawBrightness(p_46804_); } - + @Override public int getMaxLocalRawBrightness(BlockPos p_46850_, int p_46851_) { return super.getMaxLocalRawBrightness(p_46850_, p_46851_); } - + @Override public void updateNeighbourForOutputSignal(BlockPos p_46718_, Block p_46719_) { super.updateNeighbourForOutputSignal(p_46718_, p_46719_); } - + @Override public float getPathfindingCostFromLightLevels(BlockPos p_220420_) { return super.getPathfindingCostFromLightLevels(p_220420_); } - + @Override public boolean hasChunkAt(BlockPos p_46806_) { return super.hasChunkAt(p_46806_); } - + @Override public boolean hasChunkAt(int p_151578_, int p_151579_) { return super.hasChunkAt(p_151578_, p_151579_); } - + @Override public boolean hasChunksAt(BlockPos p_46833_, BlockPos p_46834_) { return super.hasChunksAt(p_46833_, p_46834_); } - + @Override public boolean hasChunksAt(int p_151573_, int p_151574_, int p_151575_, int p_151576_) { return super.hasChunksAt(p_151573_, p_151574_, p_151575_, p_151576_); } - + @Override public boolean hasChunksAt(int p_46813_, int p_46814_, int p_46815_, int p_46816_, int p_46817_, int p_46818_) { return super.hasChunksAt(p_46813_, p_46814_, p_46815_, p_46816_, p_46817_, p_46818_); } - + @Override public HolderLookup holderLookup(ResourceKey> p_249578_) { return super.holderLookup(p_249578_); } - + @Override public boolean isAreaLoaded(BlockPos center, int range) { return super.isAreaLoaded(center, range); } - + @Override public boolean isEmptyBlock(BlockPos p_46860_) { return super.isEmptyBlock(p_46860_); } - + @Override public boolean isWaterAt(BlockPos p_46802_) { return super.isWaterAt(p_46802_); } - + @Override public boolean canSeeSky(BlockPos p_45528_) { return super.canSeeSky(p_45528_); } - + @Override public BlockHitResult clip(ClipContext p_45548_) { return super.clip(p_45548_); } - + @Override @javax.annotation.Nullable public BlockHitResult clipWithInteractionOverride(Vec3 p_45559_, @@ -1685,109 +1554,97 @@ public BlockHitResult clipWithInteractionOverride(Vec3 p_45559_, { return super.clipWithInteractionOverride(p_45559_, p_45560_, p_45561_, p_45562_, p_45563_); } - + @Override public double getBlockFloorHeight(BlockPos p_45574_) { return super.getBlockFloorHeight(p_45574_); } - + @Override public double getBlockFloorHeight(VoxelShape p_45565_, Supplier p_45566_) { return super.getBlockFloorHeight(p_45565_, p_45566_); } - + @Override public Stream getBlockStates(AABB p_45557_) { return super.getBlockStates(p_45557_); } - + @Override public int getLightEmission(BlockPos p_45572_) { return super.getLightEmission(p_45572_); } - - @Override - public int getMaxLightLevel() - { - return super.getMaxLightLevel(); - } - + @Override public BlockHitResult isBlockInLine(ClipBlockStateContext p_151354_) { return super.isBlockInLine(p_151354_); } - + @Override - public int getMaxBuildHeight() + public int getMaxY() { - return super.getMaxBuildHeight(); + return super.getMaxY(); } - + @Override - public int getMaxSection() + public int getMaxSectionY() { - return super.getMaxSection(); + return super.getMaxSectionY(); } - + @Override - public int getMinSection() + public int getMinSectionY() { - return super.getMinSection(); + return super.getMinSectionY(); } - + @Override public int getSectionIndex(int p_151565_) { return super.getSectionIndex(p_151565_); } - + @Override public int getSectionIndexFromSectionY(int p_151567_) { return super.getSectionIndexFromSectionY(p_151567_); } - + @Override public int getSectionYFromSectionIndex(int p_151569_) { return super.getSectionYFromSectionIndex(p_151569_); } - + @Override public int getSectionsCount() { return super.getSectionsCount(); } - + @Override public boolean isOutsideBuildHeight(BlockPos p_151571_) { return super.isOutsideBuildHeight(p_151571_); } - + @Override public boolean isOutsideBuildHeight(int p_151563_) { return super.isOutsideBuildHeight(p_151563_); } - - @Override - public float getShade(float normalX, float normalY, float normalZ, boolean shade) - { - return super.getShade(normalX, normalY, normalZ, shade); - } - + @Override public boolean collidesWithSuffocatingBlock(@javax.annotation.Nullable Entity p_186438_, AABB p_186439_) { return super.collidesWithSuffocatingBlock(p_186438_, p_186439_); } - + @Override public Optional findFreePosition(@javax.annotation.Nullable Entity p_151419_, VoxelShape p_151420_, @@ -1798,189 +1655,137 @@ public Optional findFreePosition(@javax.annotation.Nullable Entity p_15141 { return super.findFreePosition(p_151419_, p_151420_, p_151421_, p_151422_, p_151423_, p_151424_); } - + @Override public Optional findSupportingBlock(Entity p_286468_, AABB p_286792_) { return super.findSupportingBlock(p_286468_, p_286792_); } - + @Override public Iterable getBlockCollisions(@javax.annotation.Nullable Entity p_186435_, AABB p_186436_) { return super.getBlockCollisions(p_186435_, p_186436_); } - + @Override public Iterable getCollisions(@javax.annotation.Nullable Entity p_186432_, AABB p_186433_) { return super.getCollisions(p_186432_, p_186433_); } - + @Override public boolean isUnobstructed(Entity p_45785_) { return super.isUnobstructed(p_45785_); } - + @Override public boolean isUnobstructed(BlockState p_45753_, BlockPos p_45754_, CollisionContext p_45755_) { return super.isUnobstructed(p_45753_, p_45754_, p_45755_); } - + @Override public boolean noCollision(AABB p_45773_) { return super.noCollision(p_45773_); } - + @Override public boolean noCollision(Entity p_45787_) { return super.noCollision(p_45787_); } - + @Override public boolean noCollision(@javax.annotation.Nullable Entity p_45757_, AABB p_45758_) { return super.noCollision(p_45757_, p_45758_); } - + @Override public int getBestNeighborSignal(BlockPos p_277977_) { return super.getBestNeighborSignal(p_277977_); } - + @Override public int getControlInputSignal(BlockPos p_277757_, Direction p_278104_, boolean p_277707_) { return super.getControlInputSignal(p_277757_, p_278104_, p_277707_); } - + @Override public int getDirectSignal(BlockPos p_277954_, Direction p_277342_) { return super.getDirectSignal(p_277954_, p_277342_); } - + @Override public int getDirectSignalTo(BlockPos p_277959_) { return super.getDirectSignalTo(p_277959_); } - + @Override public int getSignal(BlockPos p_277961_, Direction p_277351_) { return super.getSignal(p_277961_, p_277351_); } - + @Override public boolean hasNeighborSignal(BlockPos p_277626_) { return super.hasNeighborSignal(p_277626_); } - + @Override public boolean hasSignal(BlockPos p_277371_, Direction p_277391_) { return super.hasSignal(p_277371_, p_277391_); } - + @Override public boolean addFreshEntity(Entity p_46964_) { return super.addFreshEntity(p_46964_); } - + @Override public boolean destroyBlock(BlockPos p_46962_, boolean p_46963_) { return super.destroyBlock(p_46962_, p_46963_); } - + @Override public boolean destroyBlock(BlockPos p_46954_, boolean p_46955_, @javax.annotation.Nullable Entity p_46956_) { return super.destroyBlock(p_46954_, p_46955_, p_46956_); } - - @Override - public float getMoonBrightness() - { - return super.getMoonBrightness(); - } - - @Override - public int getMoonPhase() - { - return super.getMoonPhase(); - } - - @Override - public float getTimeOfDay(float p_46943_) - { - return super.getTimeOfDay(p_46943_); - } - - @Override - public Collection> getPartEntities() - { - return super.getPartEntities(); - } - + @Override @Nullable public AuxiliaryLightManager getAuxLightManager(BlockPos pos) { return super.getAuxLightManager(pos); } - + @Override @Nullable public AuxiliaryLightManager getAuxLightManager(ChunkPos pos) { return super.getAuxLightManager(pos); } - - @Override - public Explosion explode(Entity p_256233_, - DamageSource p_255861_, - ExplosionDamageCalculator p_255867_, - double p_256447_, - double p_255732_, - double p_255717_, - float p_256013_, - boolean p_256228_, - ExplosionInteraction p_255784_, - ParticleOptions p_311886_, - ParticleOptions p_311887_, - Holder p_320084_) - { - return super.explode(p_256233_, - p_255861_, - p_255867_, - p_256447_, - p_255732_, - p_255717_, - p_256013_, - p_256228_, - p_255784_, - p_311886_, - p_311887_, - p_320084_); - } - + @Override public void playLocalSound(Entity p_312189_, SoundEvent p_312080_, SoundSource p_312905_, float p_312914_, float p_312831_) { super.playLocalSound(p_312189_, p_312080_, p_312905_, p_312914_, p_312831_); } - + @Override - public void playSound(Player p_309201_, + public void playSound(Entity p_309201_, double p_308925_, double p_309072_, double p_308916_, @@ -1989,55 +1794,55 @@ public void playSound(Player p_309201_, { super.playSound(p_309201_, p_308925_, p_309072_, p_308916_, p_308917_, p_308902_); } - + @Override public @Nullable T setData(AttachmentType type, T data) { return super.setData(type, data); } - + @Override public boolean noBlockCollision(Entity p_295728_, AABB p_294209_) { return super.noBlockCollision(p_295728_, p_294209_); } - + @Override public T getData(Supplier> type) { return super.getData(type); } - + @Override public boolean hasData(Supplier> type) { return super.hasData(type); } - + @Override public @Nullable T setData(Supplier> type, T data) { return super.setData(type, data); } - + @Override public Optional getExistingData(AttachmentType type) { return super.getExistingData(type); } - + @Override public @Nullable T removeData(AttachmentType type) { return super.removeData(type); } - + @Override public Optional getExistingData(Supplier> type) { return super.getExistingData(type); } - + @Override public @Nullable T removeData(Supplier> type) { @@ -2045,9 +1850,27 @@ public Optional getExistingData(Supplier> type) } @Override - protected long advanceDaytime() + public @Nullable T getExistingDataOrNull(Supplier> type) { - return super.advanceDaytime(); + return super.getExistingDataOrNull(type); + } + + @Override + public void syncData(AttachmentType type) + { + super.syncData(type); + } + + @Override + public void syncData(Supplier> type) + { + super.syncData(type); + } + + @Override + public @Nullable T getExistingDataOrNull(AttachmentType type) + { + return super.getExistingDataOrNull(type); } @Override @@ -2104,5 +1927,187 @@ public String getDescriptionKey() { return super.getCapability(cap, pos, state, blockEntity, context); } + + @Override + public void addParticle(ParticleOptions particle, + boolean overrideLimiter, + boolean alwaysShow, + double x, + double y, + double z, + double xd, + double yd, + double zd) + { + super.addParticle(particle, overrideLimiter, alwaysShow, x, y, z, xd, yd, zd); + } + + @Override + public boolean canHaveWeather() + { + return super.canHaveWeather(); + } + + @Override + public long getDefaultClockTime() + { + return super.getDefaultClockTime(); + } + + @Override + public @Nullable Entity getEntity(UUID uuid) + { + return super.getEntity(uuid); + } + + @Override + public @Nullable Entity getEntityInAnyDimension(UUID uuid) + { + return super.getEntityInAnyDimension(uuid); + } + + @Override + public long getOverworldClockTime() + { + return super.getOverworldClockTime(); + } + + @Override + public @Nullable Player getPlayerInAnyDimension(UUID uuid) + { + return super.getPlayerInAnyDimension(uuid); + } + + @Override + public List getPushableEntities(Entity pusher, AABB boundingBox) + { + return super.getPushableEntities(pusher, boundingBox); + } + + @Override + public RespawnData getWorldBorderAdjustedRespawnData(RespawnData respawnData) + { + return super.getWorldBorderAdjustedRespawnData(respawnData); + } + + @Override + public boolean hasEntities(EntityTypeTest type, AABB bb, Predicate selector) + { + return super.hasEntities(type, bb, selector); + } + + @Override + public boolean isDarkOutside() + { + return super.isDarkOutside(); + } + + @Override + public boolean isInValidBounds(BlockPos pos) + { + return super.isInValidBounds(pos); + } + + @Override + public void onBlockEntityAdded(BlockEntity blockEntity) + { + super.onBlockEntityAdded(blockEntity); + } + + @Override + public void playPlayerSound(SoundEvent sound, SoundSource source, float volume, float pitch) + { + super.playPlayerSound(sound, source, volume, pitch); + } + + @Override + public Precipitation precipitationAt(BlockPos pos) + { + return super.precipitationAt(pos); + } + + @Override + public void updatePOIOnBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) + { + super.updatePOIOnBlockStateChange(pos, oldState, newState); + } + + @Override + public ScheduledTick createTick(BlockPos pos, T type, int tickDelay) + { + return super.createTick(pos, type, tickDelay); + } + + @Override + public ScheduledTick createTick(BlockPos pos, T type, int tickDelay, TickPriority priority) + { + return super.createTick(pos, type, tickDelay, priority); + } + + @Override + public int getEffectiveSkyBrightness(BlockPos pos) + { + return super.getEffectiveSkyBrightness(pos); + } + + @Override + public int getHeight(Types type, BlockPos pos) + { + return super.getHeight(type, pos); + } + + @Override + public boolean isInsideBuildHeight(int blockY) + { + return super.isInsideBuildHeight(blockY); + } + + @Override + public BlockHitResult clipIncludingBorder(ClipContext c) + { + return super.clipIncludingBorder(c); + } + + @Override + public Iterable getBlockAndLiquidCollisions(@Nullable Entity source, AABB box) + { + return super.getBlockAndLiquidCollisions(source, box); + } + + @Override + public Iterable getPreMoveCollisions(@Nullable Entity source, AABB box, Vec3 oldPos) + { + return super.getPreMoveCollisions(source, box, oldPos); + } + + @Override + public boolean noBlockCollision(@Nullable Entity entity, AABB aabb, boolean alwaysCollideWithFluids) + { + return super.noBlockCollision(entity, aabb, alwaysCollideWithFluids); + } + + @Override + public boolean noBorderCollision(@Nullable Entity entity, AABB aabb) + { + return super.noBorderCollision(entity, aabb); + } + + @Override + public boolean noCollision(@Nullable Entity entity, AABB aabb, boolean alwaysCollideWithFluids) + { + return super.noCollision(entity, aabb, alwaysCollideWithFluids); + } + + @Override + public boolean noEntityCollision(@Nullable Entity entity, AABB aabb) + { + return super.noEntityCollision(entity, aabb); + } + + @Override + public boolean isInsideBuildHeight(BlockPos pos) + { + return super.isInsideBuildHeight(pos); + } */ } diff --git a/src/main/java/com/ldtteam/common/fakelevel/FakeLevelChunkSection.java b/src/main/java/com/ldtteam/common/fakelevel/FakeLevelChunkSection.java index a0b3e10f..f08964b5 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/FakeLevelChunkSection.java +++ b/src/main/java/com/ldtteam/common/fakelevel/FakeLevelChunkSection.java @@ -3,6 +3,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeResolver; import net.minecraft.world.level.biome.Climate.Sampler; @@ -18,27 +19,28 @@ */ public class FakeLevelChunkSection extends LevelChunkSection { - private final FakeChunk fakeChunk; + private static final int SECTION_WIDTH = 16; + private static final int SECTION_HEIGHT = 16; + + private final FakeLevel fakeLevel; private final int yIdx; + private final ChunkPos chunkPos; /** - * @param fakeChunk parent chunk + * @param fakeLevel parent chunk * @param yIdx yLevel in chunk, multiply by section height */ - public FakeLevelChunkSection(final FakeChunk fakeChunk, final int yIdx) + public FakeLevelChunkSection(final FakeLevel fakeLevel, final int yIdx, final ChunkPos chunkPos) { super(null, null); - this.fakeChunk = fakeChunk; + this.fakeLevel = fakeLevel; this.yIdx = yIdx; - - // set itself to cache - fakeChunk.lastY = yIdx; - fakeChunk.lastSection = this; + this.chunkPos = chunkPos; } private BlockPos formGlobalPos(int x, int y, int z) { - return new BlockPos(x + fakeChunk.getPos().x * SECTION_WIDTH, y + yIdx * SECTION_HEIGHT, z + fakeChunk.getPos().z * SECTION_WIDTH); + return new BlockPos(x + chunkPos.x() * SECTION_WIDTH, y + yIdx * SECTION_HEIGHT, z + chunkPos.z() * SECTION_WIDTH); } @Override @@ -51,19 +53,19 @@ public BlockState setBlockState(int x, int y, int z, BlockState p_62995_, boolea @Override public BlockState getBlockState(int x, int y, int z) { - return fakeChunk.getBlockState(formGlobalPos(x, y, z)); + return fakeLevel.getBlockState(formGlobalPos(x, y, z)); } @Override public FluidState getFluidState(int x, int y, int z) { - return fakeChunk.getFluidState(formGlobalPos(x, y, z)); + return fakeLevel.getFluidState(formGlobalPos(x, y, z)); } @Override public Holder getNoiseBiome(int x, int y, int z) { - return fakeChunk.getNoiseBiome(fakeChunk.getPos().x, yIdx * SECTION_HEIGHT, fakeChunk.getPos().z); + return fakeLevel.getNoiseBiome(chunkPos.x(), yIdx * SECTION_HEIGHT, chunkPos.z()); } @Override diff --git a/src/main/java/com/ldtteam/common/fakelevel/FakeLevelData.java b/src/main/java/com/ldtteam/common/fakelevel/FakeLevelData.java index 2306d5c3..2d7d69b0 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/FakeLevelData.java +++ b/src/main/java/com/ldtteam/common/fakelevel/FakeLevelData.java @@ -1,8 +1,8 @@ package com.ldtteam.common.fakelevel; import net.minecraft.core.BlockPos; +import net.minecraft.core.GlobalPos; import net.minecraft.world.Difficulty; -import net.minecraft.world.level.GameRules; import net.minecraft.world.level.storage.LevelData; import net.minecraft.world.level.storage.WritableLevelData; import java.util.function.Supplier; @@ -22,15 +22,9 @@ protected FakeLevelData(final Supplier vanillaLevelData, final IFakeL } @Override - public BlockPos getSpawnPos() + public LevelData.RespawnData getRespawnData() { - return BlockPos.ZERO; - } - - @Override - public float getSpawnAngle() - { - return 0; + return new RespawnData(new GlobalPos(vanillaLevelData.get().getRespawnData().dimension(), BlockPos.ZERO), 0, 0); } @Override @@ -39,42 +33,12 @@ public long getGameTime() return vanillaLevelData.get().getGameTime(); } - @Override - public long getDayTime() - { - return lightProvider.forceOwnLightLevel() ? lightProvider.getDayTime() : vanillaLevelData.get().getDayTime(); - } - - @Override - public boolean isThundering() - { - return false; - } - - @Override - public boolean isRaining() - { - return false; - } - - @Override - public void setRaining(final boolean p_78171_) - { - // Noop - } - @Override public boolean isHardcore() { return false; } - @Override - public GameRules getGameRules() - { - return vanillaLevelData.get().getGameRules(); - } - @Override public Difficulty getDifficulty() { @@ -89,8 +53,8 @@ public boolean isDifficultyLocked() } @Override - public void setSpawn(final BlockPos pos, final float angle) + public void setSpawn(final LevelData.RespawnData respawnData) { - // Noop + // Noop } } diff --git a/src/main/java/com/ldtteam/common/fakelevel/FakeLevelLightEngine.java b/src/main/java/com/ldtteam/common/fakelevel/FakeLevelLightEngine.java index 4e5787d8..428d4f4e 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/FakeLevelLightEngine.java +++ b/src/main/java/com/ldtteam/common/fakelevel/FakeLevelLightEngine.java @@ -84,7 +84,7 @@ public SectionType getDebugSectionType(final LightLayer p_285008_, final Section } @Override - public boolean lightOnInSection(final SectionPos p_285319_) + public boolean lightOnInColumn(long sectionZeroNode) { // Noop, used only in chunk compiling? return false; @@ -153,14 +153,14 @@ public int getLightSectionCount() // super is fine return super.getLightSectionCount(); } - + @Override public int getMaxLightSection() { // super is fine return super.getMaxLightSection(); } - + @Override public int getMinLightSection() { @@ -229,5 +229,11 @@ public int getLightValue(final BlockPos p_75710_) { return fakeLevel.getBrightness(lightLayer, p_75710_); } + + @Override + public void updateSectionStatus(BlockPos pos, boolean sectionEmpty) + { + // Noop + } } } diff --git a/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelBlockGetter.java b/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelBlockGetter.java index 8066728f..1436c8a2 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelBlockGetter.java +++ b/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelBlockGetter.java @@ -27,7 +27,7 @@ public interface IFakeLevelBlockGetter extends BlockGetter /** * @return min X coord inclusive - * @see #getMinBuildHeight() equivalent + * @see #getMinY() equivalent */ default int getMinX() { @@ -35,14 +35,14 @@ default int getMinX() } @Override - default int getMinBuildHeight() + default int getMinY() { return 0; } /** * @return min Z coord inclusive - * @see #getMinBuildHeight() equivalent + * @see #getMinY() equivalent */ default int getMinZ() { @@ -51,20 +51,20 @@ default int getMinZ() /** * @return max X coord exclusive - * @see #getMaxBuildHeight() equivalent + * @see #getMaxY() equivalent */ default int getMaxX() { - return getMinX() + getSizeX(); + return getMinX() + getSizeX() - 1; } /** * @return max Z coord exclusive - * @see #getMaxBuildHeight() equivalent + * @see #getMaxY() equivalent */ default int getMaxZ() { - return getMinZ() + getSizeZ(); + return getMinZ() + getSizeZ() - 1; } /** @@ -74,9 +74,9 @@ default int getMaxZ() */ default boolean isPosInside(final BlockPos pos) { - return getMinX() <= pos.getX() && pos.getX() < getMaxX() && - getMinBuildHeight() <= pos.getY() && pos.getY() < getMaxBuildHeight() && - getMinZ() <= pos.getZ() && pos.getZ() < getMaxZ(); + return getMinX() <= pos.getX() && pos.getX() <= getMaxX() && + getMinY() <= pos.getY() && pos.getY() <= getMaxY() && + getMinZ() <= pos.getZ() && pos.getZ() <= getMaxZ(); } /** @@ -123,6 +123,6 @@ default BlockState getRawBlockState(final BlockPos pos) */ default AABB getAABB() { - return new AABB(getMinX(), getMinBuildHeight(), getMinZ(), getMaxX(), getMaxBuildHeight(), getMaxZ()); + return new AABB(getMinX(), getMinY(), getMinZ(), getMaxX() + 1, getMaxY() + 1, getMaxZ() + 1); } } diff --git a/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelLightProvider.java b/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelLightProvider.java index 5336e232..239f3edb 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelLightProvider.java +++ b/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelLightProvider.java @@ -1,13 +1,13 @@ package com.ldtteam.common.fakelevel; import net.minecraft.core.BlockPos; -import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.BlockAndLightGetter; import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.lighting.LightEngine; import net.neoforged.neoforge.common.ModConfigSpec.IntValue; /** - * Loosely based on {@link BlockAndTintGetter} + * Loosely based on {@link BlockAndLightGetter} */ public interface IFakeLevelLightProvider { @@ -34,7 +34,7 @@ public int getSkyDarken() /** * Returning false here means no other method from this iface will get called and all logic will be redirected to current client level. - * + * * @return false if client level should be used instead */ boolean forceOwnLightLevel(); diff --git a/src/main/java/com/ldtteam/common/fakelevel/SingleBlockFakeLevel.java b/src/main/java/com/ldtteam/common/fakelevel/SingleBlockFakeLevel.java index 87b5e3be..23d0e621 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/SingleBlockFakeLevel.java +++ b/src/main/java/com/ldtteam/common/fakelevel/SingleBlockFakeLevel.java @@ -19,7 +19,7 @@ public class SingleBlockFakeLevel extends FakeLevel T useFakeLevelContext(final BlockState blockState, @Nullable final BlockEntity blockEntity, diff --git a/src/main/java/com/ldtteam/common/language/LanguageHandler.java b/src/main/java/com/ldtteam/common/language/LanguageHandler.java index 3c3f4ae1..73c937e1 100644 --- a/src/main/java/com/ldtteam/common/language/LanguageHandler.java +++ b/src/main/java/com/ldtteam/common/language/LanguageHandler.java @@ -3,7 +3,6 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import net.minecraft.locale.Language; -import net.minecraft.network.chat.Component; import net.neoforged.fml.loading.FMLEnvironment; import org.apache.commons.io.IOUtils; import java.io.InputStream; @@ -26,33 +25,6 @@ private LanguageHandler() // Intentionally left empty. } - /** - * Localize a string and use String.format(). - * - * @param inputKey translation key. - * @param args Objects for String.format(). - * @return Localized string. - */ - @Deprecated(forRemoval = true, since = "1.21.1") - public static String format(final String key, final Object... args) - { - final String result = (args.length == 0 ? Component.translatable(key) : Component.translatable(key, args)).getString(); - return result.isEmpty() ? key : result; - } - - /** - * Translates key to readable string and formats it. - * - * @param key translation key - * @param format String.format() attributes - * @return formatted string - */ - @Deprecated(forRemoval = true, since = "1.21.1") - public static String translateKeyWithFormat(final String key, final Object... format) - { - return String.format(translateKey(key), format); - } - /** * Translates key to readable string. * @@ -92,7 +64,7 @@ private LanguageCache() private void load(final String path) { - final String locale = FMLEnvironment.dist.isClient() ? ClientLocale.getLocale() : ServerLocale.getLocale(); + final String locale = FMLEnvironment.getDist().isClient() ? ClientLocale.getLocale() : ServerLocale.getLocale(); InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(String.format(path, locale)); if (is == null) diff --git a/src/main/java/com/ldtteam/common/network/AbstractClientPlayMessage.java b/src/main/java/com/ldtteam/common/network/AbstractClientPlayMessage.java index e4f9e261..ed472638 100644 --- a/src/main/java/com/ldtteam/common/network/AbstractClientPlayMessage.java +++ b/src/main/java/com/ldtteam/common/network/AbstractClientPlayMessage.java @@ -24,7 +24,7 @@ public AbstractClientPlayMessage(final PlayMessageType type) * * @param buf received network payload * @param type message type - * @apiNote you can keep this protected to reduce visibility + * API note: you can keep this protected to reduce visibility */ protected AbstractClientPlayMessage(final RegistryFriendlyByteBuf buf, final PlayMessageType type) { diff --git a/src/main/java/com/ldtteam/common/network/AbstractPlayMessage.java b/src/main/java/com/ldtteam/common/network/AbstractPlayMessage.java index 12c39338..03eaa364 100644 --- a/src/main/java/com/ldtteam/common/network/AbstractPlayMessage.java +++ b/src/main/java/com/ldtteam/common/network/AbstractPlayMessage.java @@ -27,7 +27,7 @@ public AbstractPlayMessage(final PlayMessageType type) * * @param buf received network payload * @param type message type - * @apiNote you can keep this protected to reduce visibility + * API note: you can keep this protected to reduce visibility */ protected AbstractPlayMessage(final RegistryFriendlyByteBuf buf, final PlayMessageType type) { diff --git a/src/main/java/com/ldtteam/common/network/AbstractServerPlayMessage.java b/src/main/java/com/ldtteam/common/network/AbstractServerPlayMessage.java index da9899c9..0b581e9e 100644 --- a/src/main/java/com/ldtteam/common/network/AbstractServerPlayMessage.java +++ b/src/main/java/com/ldtteam/common/network/AbstractServerPlayMessage.java @@ -24,7 +24,7 @@ public AbstractServerPlayMessage(final PlayMessageType type) * * @param buf received network payload * @param type message type - * @apiNote you can keep this protected to reduce visibility + * API note: you can keep this protected to reduce visibility */ protected AbstractServerPlayMessage(final RegistryFriendlyByteBuf buf, final PlayMessageType type) { diff --git a/src/main/java/com/ldtteam/common/network/IServerboundDistributor.java b/src/main/java/com/ldtteam/common/network/IServerboundDistributor.java index 56e13652..5aca1245 100644 --- a/src/main/java/com/ldtteam/common/network/IServerboundDistributor.java +++ b/src/main/java/com/ldtteam/common/network/IServerboundDistributor.java @@ -1,7 +1,7 @@ package com.ldtteam.common.network; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.neoforged.neoforge.network.PacketDistributor; +import net.neoforged.neoforge.client.network.ClientPacketDistributor; /** * List of possible network targets when sending from client to server. @@ -10,6 +10,6 @@ public interface IServerboundDistributor extends CustomPacketPayload { public default void sendToServer() { - PacketDistributor.sendToServer(this); + ClientPacketDistributor.sendToServer(this); } } diff --git a/src/main/java/com/ldtteam/common/network/PlayMessageType.java b/src/main/java/com/ldtteam/common/network/PlayMessageType.java index 0889fb40..9cfe4518 100644 --- a/src/main/java/com/ldtteam/common/network/PlayMessageType.java +++ b/src/main/java/com/ldtteam/common/network/PlayMessageType.java @@ -202,25 +202,28 @@ public static PlayMessageType forBothSides(fi /** * Call this in following code: - * + * *

      * public static void onNetworkRegistry(final RegisterPayloadHandlerEvent event)
      * {
      *     final String modVersion = ModList.get().getModContainerById(Constants.MOD_ID).get().getModInfo().getVersion().toString();
      *     final PayloadRegistrar registry = event.registrar(Constants.MOD_ID).versioned(modVersion);
-     * 
+     *
      *     // MyMessage extends one of AbstractPlayMessage, AbstractClientPlayMessage, AbstractServerPlayMessage
      *     MyMessage.TYPE.register(registry);
      * }
      * 
- * + * * @param registry event network registry */ public void register(final PayloadRegistrar registry) { if (client != null && server != null) { - registry.playBidirectional(id, codec, this::onBidirectional); + // NeoForge's three-argument bidirectional overload only installs the + // server handler; the client handler must be supplied explicitly (or + // registered later through RegisterClientPayloadHandlersEvent). + registry.playBidirectional(id, codec, this::onServer, this::onClient); } else if (client != null) { diff --git a/src/main/java/com/ldtteam/common/util/BlockToItemHelper.java b/src/main/java/com/ldtteam/common/util/BlockToItemHelper.java index dc858341..2c982e8c 100644 --- a/src/main/java/com/ldtteam/common/util/BlockToItemHelper.java +++ b/src/main/java/com/ldtteam/common/util/BlockToItemHelper.java @@ -3,9 +3,11 @@ import com.ldtteam.blockui.mod.item.BlockStateRenderingData; import com.ldtteam.common.fakelevel.SingleBlockFakeLevel.SidedSingleBlockFakeLevel; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; +import net.minecraft.core.RegistryAccess; import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.ProblemReporter; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; @@ -18,24 +20,25 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.Fluids; -import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.level.storage.TagValueOutput; import net.minecraft.world.phys.HitResult; -import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.common.util.FakePlayerFactory; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Methods for getting itemStack from blockState. */ public class BlockToItemHelper { - public static final HitResult ZERO_POS_HIT_RESULT = new BlockHitResult(Vec3.atCenterOf(BlockPos.ZERO), Direction.NORTH, BlockPos.ZERO, true); private static final SidedSingleBlockFakeLevel fakeLevel = new SidedSingleBlockFakeLevel(); + private static final Logger LOGGER = LoggerFactory.getLogger(BlockToItemHelper.class); /** * Mostly for use in UI where you dont have level instance (eg. player selects block, from xml, but not when displaying real world * info - see {@link BlockStateRenderingData#of(Level, BlockPos, Player)}). - * + * * @return result of player middle-mouse-button click with more sensible defaults (liquids -> buckets, fire -> flint+steel), might * be {@link ItemStack#isEmpty()} in case of error */ @@ -52,13 +55,13 @@ public static ItemStack getItemStack(final BlockState blockState, final BlockEnt return fakeLevel.get(player.level()).useFakeLevelContext(blockState, blockEntity, player.level(), - level -> getItemStackUsingPlayerPick(level, BlockPos.ZERO, player, ZERO_POS_HIT_RESULT)); + level -> getItemStackUsingPlayerPick(level, BlockPos.ZERO, player, null)); } /** * Mostly for use by machines/entities when you dont have player instance - uses fake player. - * - * @return result of player middle-mouse-button click with more sensible defaults (liquids -> buckets, fire -> flint&steel), might + * + * @return result of player middle-mouse-button click with more sensible defaults (liquids -> buckets, fire -> flint and steel), might * be {@link ItemStack#isEmpty()} in case of error */ public static ItemStack getItemStack(final ServerLevel serverLevel, final BlockPos pos) @@ -68,8 +71,8 @@ public static ItemStack getItemStack(final ServerLevel serverLevel, final BlockP /** * General method when you have everything block->item mapping needs, but you don't have hit result (ray trace from camera). - * - * @return result of player middle-mouse-button click with more sensible defaults (liquids -> buckets, fire -> flint&steel), might + * + * @return result of player middle-mouse-button click with more sensible defaults (liquids -> buckets, fire -> flint and steel), might * be {@link ItemStack#isEmpty()} in case of error */ public static ItemStack getItemStack(final Level level, final BlockPos pos, final Player player) @@ -78,18 +81,15 @@ public static ItemStack getItemStack(final Level level, final BlockPos pos, fina } /** - * @return result of player middle-mouse-button click with more sensible defaults (liquids -> buckets, fire -> flint&steel), might + * @return result of player middle-mouse-button click with more sensible defaults (liquids -> buckets, fire -> flint and steel), might * be {@link ItemStack#isEmpty()} in case of error + * @deprecated because vanilla removed {@link HitResult} from method signature */ + @Deprecated(since = "26.1") public static ItemStack getItemStackUsingPlayerPick(final Level level, final BlockPos pos, final Player player, @Nullable HitResult hitResult) { - if (hitResult == null) - { - hitResult = new BlockHitResult(Vec3.atCenterOf(pos), Direction.NORTH, pos, true); - } - final BlockState blockState = level.getBlockState(pos); - ItemStack result = blockState.getCloneItemStack(hitResult, level, pos, player); + ItemStack result = blockState.getCloneItemStack(pos, level, true, player); if (result.isEmpty()) { @@ -121,4 +121,24 @@ else if (block instanceof BaseFireBlock) return block.asItem(); } + + /** + * Mimics vanilla logic, previously it was in BlockEntity, later moved to ServerGamePacketListenerImpl. + * + * @param blockEntity to be written + * @param itemStack to write to + * @param registryAccess from real level + */ + @SuppressWarnings("deprecation") + public static void saveBeToItem(final BlockEntity blockEntity, final ItemStack itemStack, final RegistryAccess registryAccess) + { + try (ProblemReporter.ScopedCollector reporter = new ProblemReporter.ScopedCollector(() -> "BlockUI writing block entity to item", LOGGER)) + { + TagValueOutput output = TagValueOutput.createWithContext(reporter, registryAccess); + blockEntity.saveCustomOnly(output); + blockEntity.removeComponentsFromTag(output); + BlockItem.setBlockEntityData(itemStack, blockEntity.getType(), output); + itemStack.applyComponents(blockEntity.collectComponents()); + } + } } diff --git a/src/main/java/com/ldtteam/common/util/CompoundTagToClassReflection.java b/src/main/java/com/ldtteam/common/util/CompoundTagToClassReflection.java new file mode 100644 index 00000000..9dc747ef --- /dev/null +++ b/src/main/java/com/ldtteam/common/util/CompoundTagToClassReflection.java @@ -0,0 +1,277 @@ +package com.ldtteam.common.util; + +import com.ldtteam.blockui.util.SafeError; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.EndTag; +import net.minecraft.nbt.FloatTag; +import net.minecraft.nbt.IntArrayTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.LongArrayTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.ShortTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +/** + * Mimic what codec would do, but vanilla doesn't coded that for some reason. + */ +public class CompoundTagToClassReflection +{ + public static void compoundToClassFields(final CompoundTag data, final Object target, final String errorContext) + { + final Map fields = getAllFields(target); + for (final String fieldName : data.keySet()) + { + final Field field = fields.get(fieldName); + if (field == null) + { + SafeError.throwInDev(new IllegalArgumentException(errorContext + ": cannot find field: " + fieldName)); + continue; + } + if (!field.canAccess(target)) + { + SafeError.throwInDev(new IllegalArgumentException(errorContext + ": cannot access field: " + fieldName)); + continue; + } + + final Tag value = data.get(fieldName); + + switch (value) + { + case ByteArrayTag t -> setFieldObject(field, target, t.getAsByteArray(), errorContext); + case IntArrayTag t -> setFieldObject(field, target, t.getAsIntArray(), errorContext); + case LongArrayTag t -> setFieldObject(field, target, t.getAsLongArray(), errorContext); + case StringTag t -> setFieldObject(field, target, t.value(), errorContext); + case ByteTag t -> { + if (field.getType() == byte.class) + { + try + { + field.setByte(target, t.value()); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + SafeError.throwInDev(new RuntimeException(errorContext + ": trying to set byte field - " + fieldName, e)); + } + } + else if (field.getType() == Byte.class) + { + setFieldObject(field, target, t.value(), errorContext); + } + else if (field.getType() == boolean.class) + { + try + { + field.setBoolean(target, t.value() != 0); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + SafeError + .throwInDev(new RuntimeException(errorContext + ": trying to set boolean field - " + fieldName, e)); + } + } + else if (field.getType() == Boolean.class) + { + setFieldObject(field, target, t.value() != 0, errorContext); + } + } + case DoubleTag t -> { + if (field.getType() == double.class) + { + try + { + field.setDouble(target, t.value()); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + SafeError + .throwInDev(new RuntimeException(errorContext + ": trying to set double field - " + fieldName, e)); + } + } + else if (field.getType() == Double.class) + { + setFieldObject(field, target, t.value(), errorContext); + } + } + case FloatTag t -> { + if (field.getType() == float.class) + { + try + { + field.setFloat(target, t.value()); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + SafeError.throwInDev(new RuntimeException(errorContext + ": trying to set float field - " + fieldName, e)); + } + } + else if (field.getType() == Float.class) + { + setFieldObject(field, target, t.value(), errorContext); + } + } + case IntTag t -> { + if (field.getType() == int.class) + { + try + { + field.setInt(target, t.value()); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + SafeError.throwInDev(new RuntimeException(errorContext + ": trying to set int field - " + fieldName, e)); + } + } + else if (field.getType() == Integer.class) + { + setFieldObject(field, target, t.value(), errorContext); + } + } + case LongTag t -> { + if (field.getType() == long.class) + { + try + { + field.setLong(target, t.value()); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + SafeError.throwInDev(new RuntimeException(errorContext + ": trying to set long field - " + fieldName, e)); + } + } + else if (field.getType() == Long.class) + { + setFieldObject(field, target, t.value(), errorContext); + } + } + case ShortTag t -> { + if (field.getType() == short.class) + { + try + { + field.setShort(target, t.value()); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + SafeError.throwInDev(new RuntimeException(errorContext + ": trying to set short field - " + fieldName, e)); + } + } + else if (field.getType() == Short.class) + { + setFieldObject(field, target, t.value(), errorContext); + } + } + case CompoundTag t -> { + // TODO: this means nested object + + // it can be final - just call recursion + // it can be present - just call recursion + // it can be null - either it has no-arg ctor -> recursion + // or we need special ctor '_ctor' nested compound -> ctor + recurion + // or sth more? + + Object targetValue; + try + { + targetValue = field.get(target); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + SafeError.throwInDev(new RuntimeException(errorContext + ": trying to get Object field - " + fieldName, e)); + break; + } + if (targetValue == null) + { + final var ctors = field.getType().getDeclaredConstructors(); + if (t.contains("_ctor")) + { + // TODO: find matching ctor + } + for (final var ctor : ctors) + { + if (ctor.getParameterTypes().length == 0) + { + try + { + targetValue = ctor.newInstance(); + } + catch (InstantiationException | + IllegalAccessException | + IllegalArgumentException | + InvocationTargetException e) + { + // continue + } + } + } + SafeError.requireNonNull(targetValue, + errorContext + ": trying to instantiate null target value - no matching constructor found"); + try + { + field.set(target, targetValue); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + SafeError.throwInDev(new RuntimeException(errorContext + ": trying to set Object field - " + fieldName, e)); + break; + } + } + + compoundToClassFields(t, targetValue, errorContext + " (inside field:" + fieldName + ")"); + } + case ListTag t -> { + // TODO: this means field is + // array + // list + // can be final or not (if not final create appropriate list/array, if final must match length) + } + case EndTag _ -> {} + case null -> {} + } + } + } + + private static void setFieldObject(final Field field, final Object target, final T value, final String errorContext) + { + if (field.getType() != value.getClass()) + { + SafeError.throwInDev(new IllegalArgumentException(errorContext + ": field '%s' - expected type '%s' - was '%s'" + .formatted(field.getName(), field.getType().getTypeName(), value.getClass().getTypeName()))); + return; + } + + try + { + field.set(target, value); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + SafeError.throwInDev(new RuntimeException(errorContext + ": trying to set field '%s' - expected type '%s' - was '%s'" + .formatted(field.getName(), field.getType().getTypeName(), value.getClass().getTypeName()), e)); + } + return; + } + + private static Map getAllFields(final Object object) + { + final Map fields = new HashMap<>(); + Class clazz = object.getClass(); + while (clazz != Object.class) + { + for (final Field field : clazz.getDeclaredFields()) + { + fields.put(field.getName(), field); + } + clazz = clazz.getSuperclass(); + } + return fields; + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 46562959..f797047f 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -2,33 +2,33 @@ public net.minecraft.world.entity.LivingEntity dead # style full ctor -public net.minecraft.network.chat.Style (Lnet/minecraft/network/chat/TextColor;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/network/chat/HoverEvent;Ljava/lang/String;Lnet/minecraft/resources/ResourceLocation;)V +public net.minecraft.network.chat.Style (Lnet/minecraft/network/chat/TextColor;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/network/chat/HoverEvent;Ljava/lang/String;Lnet/minecraft/network/chat/FontDescription;)V # gui graphics overload -public net.minecraft.client.gui.GuiGraphics (Lnet/minecraft/client/Minecraft;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;)V -public net.minecraft.client.gui.GuiGraphics minecraft +public net.minecraft.client.gui.GuiGraphicsExtractor (Lnet/minecraft/client/Minecraft;Lorg/joml/Matrix3x2fStack;Lnet/minecraft/client/renderer/state/gui/GuiRenderState;II)V +public net.minecraft.client.gui.GuiGraphicsExtractor minecraft +public net.minecraft.client.gui.GuiGraphicsExtractor guiRenderState +public net.minecraft.client.renderer.state.gui.GuiRenderState firstStratumAfterBlur # sprite loading public net.minecraft.server.packs.resources.FallbackResourceManager convertToMetadata(Lnet/minecraft/server/packs/resources/IoSupplier;)Lnet/minecraft/server/packs/resources/IoSupplier; # cursor -public com.mojang.blaze3d.platform.NativeImage pixels -public com.mojang.blaze3d.vertex.PoseStack poseStack - -# blockstate rendering -public net.minecraft.client.resources.model.ModelBakery modelResources -public net.minecraft.client.resources.model.ModelBakery topLevelModels - -# texture atlases -public net.minecraft.client.gui.GuiSpriteManager METADATA_SECTIONS -public net.minecraft.client.resources.TextureAtlasHolder textureAtlas +public com.mojang.blaze3d.platform.cursor.CursorType (Ljava/lang/String;J)V +public com.mojang.blaze3d.platform.cursor.CursorType name # vanilla button textures public net.minecraft.client.gui.components.AbstractButton SPRITES # out of jar resloc -public-f net.minecraft.resources.ResourceLocation -protected net.minecraft.resources.ResourceLocation (Ljava/lang/String;Ljava/lang/String;)V -public net.minecraft.client.renderer.texture.HttpTexture file +public-f net.minecraft.resources.Identifier +protected net.minecraft.resources.Identifier (Ljava/lang/String;Ljava/lang/String;)V + +# tooltip +public net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil * +public net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil *() -public net.minecraft.network.chat.Style (Lnet/minecraft/network/chat/TextColor;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/network/chat/HoverEvent;Ljava/lang/String;Lnet/minecraft/network/chat/FontDescription;)V # Style \ No newline at end of file +# blockstate item renderer +public net.minecraft.client.renderer.item.ItemStackRenderState firstLayer()Lnet.minecraft.client.renderer.item.ItemStackRenderState$LayerRenderState; +public net.minecraft.client.renderer.item.ItemStackRenderState$LayerRenderState itemTransform +public net.minecraft.client.renderer.item.ItemStackRenderState$LayerRenderState localTransform diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index 1b4f7854..babf372c 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -1,12 +1,28 @@ -modLoader="javafml" #mandatory +modLoader="javafml" +loaderVersion="${fml_range}" license="GPL3" -loaderVersion="[4,)" #mandatory issueTrackerURL="https://github.com/ldtteam/BlockUI/issues/new/choose" -[[mods]] #mandatory -modId="blockui" #mandatory -version="${file.jarVersion}" #mandatory -displayName="BlockUI" #mandatory -authors="LDTTeam" #optional + +[[mods]] +modId="blockui" +version="${file.jarVersion}" +displayName="UI Library Mod" +credits="LDT Team" +authors="LDT Team" description=''' -XML Based Minecraft UI Library -''' \ No newline at end of file +UI Library mod, use ctrl+alt+shift+x to open a test-ui +''' + +[[dependencies.blockui]] + modId="neoforge" + type="required" + versionRange="${neoforge_range}" + ordering="NONE" + side="BOTH" + +[[dependencies.blockui]] + modId="minecraft" + type="required" + versionRange="${minecraft_range}" + ordering="NONE" + side="BOTH" diff --git a/src/main/resources/assets/blockui/gui/block_ui.xsd b/src/main/resources/assets/blockui/gui/block_ui.xsd index daf13ffc..ef5f1a06 100644 --- a/src/main/resources/assets/blockui/gui/block_ui.xsd +++ b/src/main/resources/assets/blockui/gui/block_ui.xsd @@ -36,6 +36,7 @@ + @@ -74,6 +75,7 @@ + @@ -232,6 +234,8 @@ + + diff --git a/src/main/resources/assets/blockui/gui/test.xml b/src/main/resources/assets/blockui/gui/test.xml index 7e073454..af4538fa 100644 --- a/src/main/resources/assets/blockui/gui/test.xml +++ b/src/main/resources/assets/blockui/gui/test.xml @@ -7,15 +7,14 @@ - + - - + +