From 9882226d9e27f471e126f6ae46c12c5ae99c9903 Mon Sep 17 00:00:00 2001 From: MrFruitDude Date: Fri, 4 Sep 2026 11:47:15 -0400 Subject: [PATCH] WIP: port BlockUI to Minecraft 26.2 --- build.gradle | 67 +- gradle.properties | 33 +- gradle/wrapper/gradle-wrapper.jar | Bin 43705 -> 48966 bytes gradlew | 9 +- gradlew.bat | 3 +- settings.gradle | 5 +- .../com/ldtteam/blockui/AtlasManager.java | 121 -- .../com/ldtteam/blockui/BOGuiGraphics.java | 174 +-- .../java/com/ldtteam/blockui/BOScreen.java | 140 +- src/main/java/com/ldtteam/blockui/Loader.java | 21 +- src/main/java/com/ldtteam/blockui/Pane.java | 131 +- .../java/com/ldtteam/blockui/PaneParams.java | 70 +- .../com/ldtteam/blockui/UiRenderMacros.java | 915 ++++++++++++ .../blockui/controls/AbstractTextBuilder.java | 117 +- .../blockui/controls/AbstractTextElement.java | 36 +- .../ldtteam/blockui/controls/ButtonImage.java | 41 +- .../ldtteam/blockui/controls/CheckBox.java | 14 +- .../ldtteam/blockui/controls/EntityIcon.java | 256 +++- .../ldtteam/blockui/controls/Gradient.java | 2 +- .../com/ldtteam/blockui/controls/Image.java | 156 ++- .../ldtteam/blockui/controls/ItemIcon.java | 53 +- .../controls/ItemIconWithBlockState.java | 38 +- .../controls/ItemIconWithProperties.java | 154 --- .../ldtteam/blockui/controls/Scrollbar.java | 8 +- .../ldtteam/blockui/controls/TextField.java | 92 +- .../blockui/controls/TextFieldVanilla.java | 6 +- .../com/ldtteam/blockui/controls/Tooltip.java | 102 +- .../ldtteam/blockui/hooks/HookManager.java | 23 +- .../ldtteam/blockui/hooks/HookRegistries.java | 25 +- .../com/ldtteam/blockui/hooks/HookScreen.java | 24 +- .../com/ldtteam/blockui/hooks/HookWindow.java | 4 +- .../java/com/ldtteam/blockui/mod/BlockUI.java | 13 + .../blockui/mod/ClientEventSubscriber.java | 92 +- .../mod/ClientLifecycleSubscriber.java | 51 +- .../blockui/mod/container/ContainerHook.java | 9 +- .../mod/item/BlockStatePipRenderer.java | 335 +++++ .../mod/item/BlockStateRenderingData.java | 61 +- .../com/ldtteam/blockui/util/SafeError.java | 33 +- .../blockui/util/SingleBlockGetter.java | 31 +- .../blockui/util/SpacerTextComponent.java | 3 +- .../blockui/util/ToggleableTextComponent.java | 6 +- .../blockui/util/color/ColourARGB.java | 13 +- .../blockui/util/color/ColourQuartet4f.java | 62 + ...olourQuartet.java => ColourQuartet4i.java} | 4 +- .../blockui/util/color/ColourRGBA.java | 13 +- .../util/color/ColouredVertexConsumer.java | 76 - .../ldtteam/blockui/util/color/IColour.java | 79 +- .../ldtteam/blockui/util/cursor/Cursor.java | 99 ++ .../util/resloc/OutOfJarResourceLocation.java | 51 +- .../blockui/util/texture/CursorTexture.java | 126 ++ .../blockui/util/texture/IsOurTexture.java | 2 +- .../util/texture/MissingCursorTexture.java | 40 - .../blockui/util/texture/OutOfJarTexture.java | 68 +- .../util/texture/ResolvedWidgetSprites.java | 79 ++ .../com/ldtteam/blockui/views/BOWindow.java | 90 +- .../java/com/ldtteam/blockui/views/Box.java | 2 +- .../ldtteam/blockui/views/OverlayView.java | 10 +- .../blockui/views/ScrollingContainer.java | 10 +- .../ldtteam/blockui/views/ScrollingView.java | 2 +- .../java/com/ldtteam/blockui/views/View.java | 11 +- .../ldtteam/blockui/views/ZoomDragView.java | 52 +- .../java/com/ldtteam/common/codec/Codecs.java | 2 +- .../java/com/ldtteam/common/codec/XmlOps.java | 1232 +++++++++++++++++ .../com/ldtteam/common/codec/XmlValue.java | 251 ++++ .../common/config/AbstractConfiguration.java | 15 +- .../ldtteam/common/config/Configurations.java | 4 +- .../ldtteam/common/fakelevel/FakeChunk.java | 377 ++--- .../common/fakelevel/FakeChunkSource.java | 20 +- .../ldtteam/common/fakelevel/FakeLevel.java | 1035 +++++++------- .../fakelevel/FakeLevelChunkSection.java | 26 +- .../common/fakelevel/FakeLevelData.java | 46 +- .../fakelevel/FakeLevelLightEngine.java | 12 +- .../fakelevel/IFakeLevelBlockGetter.java | 22 +- .../fakelevel/IFakeLevelLightProvider.java | 6 +- .../fakelevel/SingleBlockFakeLevel.java | 18 +- .../common/language/LanguageHandler.java | 30 +- .../network/AbstractClientPlayMessage.java | 2 +- .../common/network/AbstractPlayMessage.java | 2 +- .../network/AbstractServerPlayMessage.java | 2 +- .../network/IServerboundDistributor.java | 4 +- .../common/network/PlayMessageType.java | 11 +- .../common/util/BlockToItemHelper.java | 54 +- .../util/CompoundTagToClassReflection.java | 277 ++++ .../resources/META-INF/accesstransformer.cfg | 34 +- .../resources/META-INF/neoforge.mods.toml | 34 +- .../resources/assets/blockui/gui/block_ui.xsd | 4 + .../resources/assets/blockui/gui/test.xml | 15 +- .../resources/assets/blockui/gui/test2.xml | 6 +- .../resources/assets/blockui/gui/test4.xml | 2 +- .../gui/blockui_sprites/repeat_blit.png | Bin 0 -> 416 bytes .../blockui_sprites/repeat_blit.png.mcmeta | 10 + .../blockui_sprites/repeat_blit_stretch.png | Bin 0 -> 416 bytes .../repeat_blit_stretch.png.mcmeta | 11 + .../blockui/common/codec/XmlOpsTest.java | 619 +++++++++ .../blockui/util/color/IColourTest.java | 2 +- 95 files changed, 6462 insertions(+), 2086 deletions(-) delete mode 100644 src/main/java/com/ldtteam/blockui/AtlasManager.java create mode 100644 src/main/java/com/ldtteam/blockui/UiRenderMacros.java delete mode 100644 src/main/java/com/ldtteam/blockui/controls/ItemIconWithProperties.java create mode 100644 src/main/java/com/ldtteam/blockui/mod/item/BlockStatePipRenderer.java create mode 100644 src/main/java/com/ldtteam/blockui/util/color/ColourQuartet4f.java rename src/main/java/com/ldtteam/blockui/util/color/{ColourQuartet.java => ColourQuartet4i.java} (77%) delete mode 100644 src/main/java/com/ldtteam/blockui/util/color/ColouredVertexConsumer.java create mode 100644 src/main/java/com/ldtteam/blockui/util/cursor/Cursor.java create mode 100644 src/main/java/com/ldtteam/blockui/util/texture/CursorTexture.java delete mode 100644 src/main/java/com/ldtteam/blockui/util/texture/MissingCursorTexture.java create mode 100644 src/main/java/com/ldtteam/blockui/util/texture/ResolvedWidgetSprites.java create mode 100644 src/main/java/com/ldtteam/common/codec/XmlOps.java create mode 100644 src/main/java/com/ldtteam/common/codec/XmlValue.java create mode 100644 src/main/java/com/ldtteam/common/util/CompoundTagToClassReflection.java create mode 100644 src/main/resources/assets/blockui/textures/gui/blockui_sprites/repeat_blit.png create mode 100644 src/main/resources/assets/blockui/textures/gui/blockui_sprites/repeat_blit.png.mcmeta create mode 100644 src/main/resources/assets/blockui/textures/gui/blockui_sprites/repeat_blit_stretch.png create mode 100644 src/main/resources/assets/blockui/textures/gui/blockui_sprites/repeat_blit_stretch.png.mcmeta create mode 100644 src/test/java/com/ldtteam/blockui/common/codec/XmlOpsTest.java 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 9bbc975c742b298b441bfb90dbc124400a3751b9..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 40550 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
irPMAS;~ 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 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 @@ - + - - + +