From 25263d7e491b4a409e8dbd7130aadb207e526f2f Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 01:35:14 +0200 Subject: [PATCH 01/15] Port to MC 26.1.1 --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index f952c16..8316edb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,7 +12,7 @@ plugins { } // This should match the folder name of the project, or else IDEA may complain (see https://youtrack.jetbrains.com/issue/IDEA-317606) -rootProject.name = "fancy-entity-renderer" +rootProject.name = "fer-26.1.1" include("common") include("fabric") include("neoforge") From ea75fafd067059466f204c8015a2f949921c020e Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 03:13:14 +0200 Subject: [PATCH 02/15] Port to MC 26.1.1 --- build.gradle | 9 ++- .../src/main/groovy/multiloader-common.gradle | 7 +- .../src/main/groovy/multiloader-loader.gradle | 29 ++----- common/build.gradle | 26 +++---- .../entity/player/FancyPlayerRenderer.java | 52 +++++++++++-- .../api/entity/player/FancyPlayerWidget.java | 43 ++++++++--- .../player/model/FancyPlayerCapeModel.java | 3 +- .../entity/player/model/FancyPlayerModel.java | 5 +- .../mixin/client/GuiEntityRendererMixin.java | 9 ++- .../fancy_entity_renderer.mixins.json | 2 - fabric/build.gradle | 75 +++++++++++++++---- .../mixin/client/GuiRendererMixin.java | 19 +++-- .../PictureInPictureRendererAccessor.java | 18 +++++ fabric/src/main/resources/fabric.mod.json | 1 - .../fancy_entity_renderer.fabric.mixins.json | 4 +- gradle.properties | 21 +++--- gradle/gradle-daemon-jvm.properties | 1 + gradle/wrapper/gradle-wrapper.properties | 2 +- neoforge/build.gradle | 11 +-- 19 files changed, 225 insertions(+), 112 deletions(-) create mode 100644 fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/accessor/PictureInPictureRendererAccessor.java create mode 100644 gradle/gradle-daemon-jvm.properties diff --git a/build.gradle b/build.gradle index 7afd029..49cfb6e 100644 --- a/build.gradle +++ b/build.gradle @@ -2,9 +2,9 @@ plugins { id "org.sonarqube" version "4.4.1.3373" id "com.hypherionmc.modutils.modpublisher" version "2.1.4" apply false // https://projects.neoforged.net/neoforged/moddevgradle - id "net.neoforged.moddev" version "2.0.126" apply false + id "net.neoforged.moddev" version "2.0.141" apply false // https://fabricmc.net/develop/ - id "fabric-loom" version "1.13-SNAPSHOT" apply false + id "net.fabricmc.fabric-loom" version "${fabric_loom_version}" apply false } sonar { @@ -17,6 +17,11 @@ sonar { } } +tasks.named("updateDaemonJvm") { + languageVersion = JavaLanguageVersion.of(java_version as Integer) + toolchainPlatforms = [] +} + tasks.register("publishAll") { group = "publishing" description = "Publish all loader specific versions to all platforms" diff --git a/buildSrc/src/main/groovy/multiloader-common.gradle b/buildSrc/src/main/groovy/multiloader-common.gradle index da906cc..1ce43d1 100644 --- a/buildSrc/src/main/groovy/multiloader-common.gradle +++ b/buildSrc/src/main/groovy/multiloader-common.gradle @@ -116,7 +116,10 @@ processResources { "fabric_loader_version" : fabric_loader_version, "neoforge_version" : neoforge_version, "neoforge_loader_version_range": neoforge_loader_version_range, - "prometheus_version" : prometheus_version + "prometheus_version" : prometheus_version, + "prometheus_minecraft_version" : prometheus_minecraft_version, + "cobweb_version" : cobweb_version, + "cobweb_minecraft_version" : cobweb_minecraft_version ] filesMatching(["pack.mcmeta", "fabric.mod.json", "META-INF/neoforge.mods.toml", "META-INF/mods.toml", "*.mixins.json"]) { expand expandProps @@ -201,7 +204,7 @@ publisher { } setGameVersions(minecraft_version) setDisplayName("[$loader - $minecraft_version] $mod_title v$mod_version") - setJavaVersions([JavaVersion.VERSION_21]) // Remember to update this to match java_version gradle property. + setJavaVersions([JavaVersion.VERSION_25]) // Remember to update this to match java_version gradle property. setCurseEnvironment("client") // "server", "client" or "both". github { repo = "$github_user/$mod_id_kebab" diff --git a/buildSrc/src/main/groovy/multiloader-loader.gradle b/buildSrc/src/main/groovy/multiloader-loader.gradle index f7fba5d..bc4d17c 100644 --- a/buildSrc/src/main/groovy/multiloader-loader.gradle +++ b/buildSrc/src/main/groovy/multiloader-loader.gradle @@ -2,44 +2,27 @@ plugins { id "multiloader-common" } -configurations { - commonJava{ - canBeResolved = true - } - commonResources{ - canBeResolved = true - } -} - dependencies { compileOnly(project(":common")) { capabilities { requireCapability "$group:$mod_id" } } - commonJava project(path: ":common", configuration: "commonJava") - commonResources project(path: ":common", configuration: "commonResources") } -tasks.named('compileJava', JavaCompile) { - dependsOn(configurations.commonJava) - source(configurations.commonJava) +tasks.named("compileJava", JavaCompile) { + source(project(":common").sourceSets.main.allSource) } processResources { - dependsOn(configurations.commonResources) - from(configurations.commonResources) + from(project(":common").sourceSets.main.resources) } -tasks.named("javadoc", Javadoc).configure { - dependsOn(configurations.commonJava) - source(configurations.commonJava) +tasks.named("javadoc", Javadoc) { + source(project(":common").sourceSets.main.allJava) } tasks.named("sourcesJar", Jar) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE - dependsOn(configurations.commonJava) - from(configurations.commonJava) - dependsOn(configurations.commonResources) - from(configurations.commonResources) + from(project(":common").sourceSets.main.allSource) } diff --git a/common/build.gradle b/common/build.gradle index 1e75f0c..d735c6d 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,25 +1,17 @@ plugins { id "multiloader-common" - id "net.neoforged.moddev" + id "net.fabricmc.fabric-loom" } -neoForge { - neoFormVersion = neo_form_version - def at = file("src/main/resources/META-INF/accesstransformer.cfg") - if (at.exists()) { - accessTransformers.from(at.absolutePath) - } - parchment { - minecraftVersion = parchment_minecraft - mappingsVersion = parchment_version - } -} +def minecraftClientJar = files(new File(gradle.gradleUserHomeDir, "caches/fabric-loom/${minecraft_version}/minecraft-client.jar")) dependencies { + minecraft "com.mojang:minecraft:$minecraft_version" compileOnly group: "org.spongepowered", name: "mixin", version: "0.8.5" compileOnly group: "io.github.llamalad7", name: "mixinextras-common", version: "$mixin_extras_version" annotationProcessor group: "io.github.llamalad7", name: "mixinextras-common", version: "$mixin_extras_version" - compileOnly "it.crystalnest:prometheus-$name:$minecraft_version-$prometheus_version" + compileOnly "it.crystalnest:prometheus-$name:$prometheus_minecraft_version-$prometheus_version" + compileOnly "it.crystalnest:cobweb-common:$cobweb_minecraft_version-$cobweb_version" } configurations { @@ -42,3 +34,11 @@ artifacts { tasks.named("publishModrinth").get().setEnabled(false) tasks.named("publishCurseforge").get().setEnabled(false) tasks.named("publishGitHub").get().setEnabled(false) + +tasks.named("compileJava", JavaCompile) { + classpath += minecraftClientJar + configurations.minecraftLibraries +} + +tasks.named("javadoc", Javadoc) { + classpath += minecraftClientJar + configurations.minecraftLibraries +} diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerRenderer.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerRenderer.java index cbe14b3..07da606 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerRenderer.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerRenderer.java @@ -9,7 +9,6 @@ import net.minecraft.client.gui.Font; import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.player.AbstractClientPlayer; -import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.entity.ArmorModelSet; import net.minecraft.client.renderer.entity.EntityRendererProvider; @@ -17,15 +16,16 @@ import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer; import net.minecraft.client.renderer.entity.player.AvatarRenderer; import net.minecraft.client.renderer.entity.state.AvatarRenderState; -import net.minecraft.client.renderer.state.CameraRenderState; import net.minecraft.network.chat.Component; import net.minecraft.util.FormattedCharSequence; +import net.minecraft.util.LightCoordsUtil; import net.minecraft.util.Mth; import net.minecraft.world.entity.Avatar; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Pose; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.phys.Vec3; +import net.minecraft.client.renderer.state.level.CameraRenderState; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; @@ -34,6 +34,10 @@ * Custom player renderer. */ public class FancyPlayerRenderer extends AvatarRenderer<@NotNull AbstractClientPlayer> { + private static final float SLEEPING_EYE_HEIGHT = 0.2F; + private static final float CROUCHING_EYE_HEIGHT = 1.27F; + private static final float PRONE_EYE_HEIGHT = 0.4F; + /** * Adult player model. */ @@ -78,11 +82,12 @@ public FancyPlayerRenderer(EntityRendererProvider.Context context, boolean isSli * @param camera camera state. */ @Override - protected void submitNameTag(@NotNull AvatarRenderState renderState, @NotNull PoseStack poseStack, @NotNull SubmitNodeCollector submitNodeCollector, @NotNull CameraRenderState camera) { + protected void submitNameDisplay(@NotNull AvatarRenderState renderState, @NotNull PoseStack poseStack, @NotNull SubmitNodeCollector submitNodeCollector, @NotNull CameraRenderState camera) { if (renderState instanceof FancyPlayerRenderState state && state.nameTag != null && state.nameTagAttachment != null) { FormattedCharSequence text = state.nameTag.getVisualOrderText(); Minecraft minecraft = Minecraft.getInstance(); poseStack.pushPose(); + int offset = state.showExtraEars ? -10 : 0; float scale = state.scale * NAMETAG_SCALE; float height = state.isBaby && state.pose != Pose.SPIN_ATTACK ? state.boundingBoxHeight * LivingEntity.DEFAULT_BABY_SCALE : state.boundingBoxHeight; float offsetY = (state.pose == Pose.SLEEPING || state.pose == Pose.SWIMMING ? state.boundingBoxWidth : height) / scale + (float) state.nameTagAttachment.y; @@ -95,9 +100,33 @@ protected void submitNameTag(@NotNull AvatarRenderState renderState, @NotNull Po poseStack.rotateAround(Axis.ZP.rotationDegrees(Math.min(Mth.sqrt((state.deathTime - 1) / 20F * 1.6F), 1) * 90), 0, offsetY, 0); } float x = -minecraft.font.width(text) / 2F; - submitNodeCollector.submitText(poseStack, x, 0, text, false, Font.DisplayMode.SEE_THROUGH, state.lightCoords, -2130706433, (int) (minecraft.options.getBackgroundOpacity(0.25F) * 255F) << 24, 0); - submitNodeCollector.submitText(poseStack, x, 0, text, false, Font.DisplayMode.NORMAL, LightTexture.lightCoordsWithEmission(state.lightCoords, 2), state.isDiscrete ? -2130706433 : -1, 0, 0); + submitNodeCollector.submitText( + poseStack, + x, + offset, + text, + false, + Font.DisplayMode.SEE_THROUGH, + state.lightCoords, + -2130706433, + (int) (minecraft.options.getBackgroundOpacity(0.25F) * 255F) << 24, + 0 + ); + submitNodeCollector.submitText( + poseStack, + x, + offset, + text, + false, + Font.DisplayMode.NORMAL, + LightCoordsUtil.lightCoordsWithEmission(state.lightCoords, 2), + state.isDiscrete ? -2130706433 : -1, + 0, + 0 + ); poseStack.popPose(); + } else { + super.submitNameDisplay(renderState, poseStack, submitNodeCollector, camera); } } @@ -202,7 +231,7 @@ protected void mimicRenderState(@NotNull FancyPlayerRenderState state, float hei * @param state render state. */ protected void updateRenderState(@NotNull FancyPlayerRenderState state) { - state.eyeHeight = Avatar.POSES.get(state.pose).eyeHeight(); + state.eyeHeight = getEyeHeight(state.pose); state.isDiscrete = state.isCrouching || state.isInvisible; if (state.isMoving && state.pose != Pose.DYING) { float step = state.speedValue * 0.33F; @@ -220,7 +249,7 @@ protected void updateRenderState(@NotNull FancyPlayerRenderState state) { } state.nameTag = state.showPlayerName && !state.isInvisibleToPlayer ? Component.literal(state.name) : null; if (state.pose == Pose.SLEEPING) { - state.nameTagAttachment = new Vec3(Avatar.POSES.get(state.pose).eyeHeight() * state.scale - state.boundingBoxHeight / 1.35F, 0, 0); + state.nameTagAttachment = new Vec3(getEyeHeight(state.pose) * state.scale - state.boundingBoxHeight / 1.35F, 0, 0); } else { state.nameTagAttachment = new Vec3(0, 0.25F * state.scale, 0); } @@ -233,4 +262,13 @@ protected void updateRenderState(@NotNull FancyPlayerRenderState state) { state.elytraRotX = (float) (Math.PI / 16); state.elytraRotZ = (float) (Math.PI / 10); } + + private static float getEyeHeight(Pose pose) { + return switch (pose) { + case SLEEPING -> SLEEPING_EYE_HEIGHT; + case FALL_FLYING, SWIMMING, SPIN_ATTACK -> PRONE_EYE_HEIGHT; + case CROUCHING -> CROUCHING_EYE_HEIGHT; + default -> Avatar.DEFAULT_EYE_HEIGHT; + }; + } } diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java index 8ba0f9d..78f9591 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java @@ -10,12 +10,11 @@ import it.crystalnest.fancy_entity_renderer.compat.Prometheus; import it.crystalnest.fancy_entity_renderer.platform.Services; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.narration.NarrationElementOutput; import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.player.AbstractClientPlayer; -import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.resources.DefaultPlayerSkin; import net.minecraft.client.sounds.SoundManager; import net.minecraft.commands.arguments.item.ItemInput; @@ -23,6 +22,7 @@ import net.minecraft.core.HolderLookup; import net.minecraft.network.chat.CommonComponents; import net.minecraft.resources.Identifier; +import net.minecraft.util.LightCoordsUtil; import net.minecraft.world.entity.Avatar; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.HumanoidArm; @@ -58,7 +58,12 @@ public class FancyPlayerWidget extends AbstractWidget { /** * Player eye height when crouching. */ - public static final float PLAYER_CROUCHING_EYE_HEIGHT = Avatar.POSES.get(Pose.CROUCHING).eyeHeight(); + public static final float PLAYER_CROUCHING_EYE_HEIGHT = 1.27F; + + /** + * Player hitbox height when crouching. + */ + public static final float PLAYER_CROUCHING_HEIGHT = 1.5F; /** * Ratio of a player's height to its width. @@ -88,7 +93,7 @@ public class FancyPlayerWidget extends AbstractWidget { */ public FancyPlayerWidget(int x, int y, int width, int height) { super(x, y, width, height, CommonComponents.EMPTY); - renderState.lightCoords = LightTexture.FULL_BRIGHT; + renderState.lightCoords = LightCoordsUtil.FULL_BRIGHT; } /** @@ -133,8 +138,8 @@ private static Optional handlePlayerCopyError(Throwable error) { */ private static ItemStack parseItem(String item, HolderLookup.Provider provider) { try { - ItemParser.ItemResult result = new ItemParser(provider).parse(new StringReader(item)); - return new ItemInput(result.item(), result.components()).createItemStack(1, false); + ItemInput result = new ItemParser(provider).parse(new StringReader(item)); + return result.createItemStack(1); } catch (CommandSyntaxException e) { Constants.LOGGER.error("Error parsing {}", item, e); return ItemStack.EMPTY; @@ -150,7 +155,7 @@ private static ItemStack parseItem(String item, HolderLookup.Provider provider) * @param partialTick partial tick. */ @Override - protected void renderWidget(@NotNull GuiGraphics gfx, int mouseX, int mouseY, float partialTick) { + protected void extractWidgetRenderState(@NotNull GuiGraphicsExtractor gfx, int mouseX, int mouseY, float partialTick) { updateRenderState(getX(), getY(), getWidth(), getHeight(), mouseX, mouseY, partialTick); float offsetX = 0; // Taken from AvatarRenderer#getRenderOffset(AvatarRenderState) @@ -164,7 +169,7 @@ protected void renderWidget(@NotNull GuiGraphics gfx, int mouseX, int mouseY, fl offsetY /= 1.5F; } } - gfx.submitEntityRenderState( + gfx.entity( renderState, 1, new Vector3f( @@ -1199,7 +1204,7 @@ public FancyPlayerWidget setAllowedPoses(List poses) { * @return {@code this}. */ public FancyPlayerWidget setPose(Pose pose) { - if (Avatar.POSES.containsKey(pose) && pose != Pose.FALL_FLYING) { + if (isSupportedPose(pose)) { renderState.pose = pose; renderState.isAutoSpinAttack = pose == Pose.SPIN_ATTACK; renderState.isCrouching = pose == Pose.CROUCHING; @@ -1329,7 +1334,23 @@ private void updateSkin(@Nullable PlayerSkin skin) { */ private void updateIsSlim(boolean isSlim) { renderState.isSlim = isSlim; - renderState.skin = DefaultPlayerSkin.DEFAULT_SKINS[random.nextInt(9) + (isSlim ? 0 : 9)]; + renderState.skin = getRandomDefaultSkin(isSlim); + } + + private PlayerSkin getRandomDefaultSkin(boolean isSlim) { + PlayerModelType modelType = isSlim ? PlayerModelType.SLIM : PlayerModelType.WIDE; + PlayerSkin skin; + do { + skin = DefaultPlayerSkin.get(new UUID(random.nextLong(), random.nextLong())); + } while (skin.model() != modelType); + return skin; + } + + private static boolean isSupportedPose(Pose pose) { + return switch (pose) { + case STANDING, SLEEPING, SWIMMING, SPIN_ATTACK, CROUCHING, DYING -> true; + default -> false; + }; } /** @@ -1346,7 +1367,7 @@ private void updateIsSlim(boolean isSlim) { protected void updateRenderState(int x, int y, int width, int height, int mouseX, int mouseY, float partialTick) { renderState.updateScale(height); if (renderState.bodyFollowsMouse || renderState.headFollowsMouse) { - float renderHeight = renderState.pose == Pose.CROUCHING ? Avatar.CROUCH_BB_HEIGHT : PLAYER_RENDER_HEIGHT; + float renderHeight = renderState.pose == Pose.CROUCHING ? PLAYER_CROUCHING_HEIGHT : PLAYER_RENDER_HEIGHT; float eyeHeight = renderState.pose == Pose.CROUCHING ? PLAYER_CROUCHING_EYE_HEIGHT : Avatar.DEFAULT_EYE_HEIGHT; float adultEyeY = (renderHeight - eyeHeight) * height / renderHeight; float eyeY = (renderState.isBaby ? (height + adultEyeY) * LivingEntity.DEFAULT_BABY_SCALE : adultEyeY); diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/model/FancyPlayerCapeModel.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/model/FancyPlayerCapeModel.java index 3789c72..69e0bae 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/model/FancyPlayerCapeModel.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/model/FancyPlayerCapeModel.java @@ -2,6 +2,7 @@ import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.model.geom.EntityModelSet; +import net.minecraft.client.model.geom.LayerDefinitions; import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.model.geom.ModelPart; import net.minecraft.client.model.geom.builders.LayerDefinition; @@ -27,7 +28,7 @@ public FancyPlayerCapeModel(EntityModelSet modelSet, boolean isBaby) { * @return correct model part. */ private static ModelPart getModelPart(EntityModelSet modelSet, boolean isBaby) { - LayerDefinition layerDefinition = modelSet.roots.get(ModelLayers.PLAYER_CAPE); + LayerDefinition layerDefinition = LayerDefinitions.createRoots().get(ModelLayers.PLAYER_CAPE); if (isBaby) { layerDefinition = layerDefinition.apply(BABY_TRANSFORMER); } diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/model/FancyPlayerModel.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/model/FancyPlayerModel.java index b87c798..5d20831 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/model/FancyPlayerModel.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/model/FancyPlayerModel.java @@ -1,6 +1,7 @@ package it.crystalnest.fancy_entity_renderer.api.entity.player.model; import it.crystalnest.fancy_entity_renderer.api.entity.player.state.FancyPlayerRenderState; +import net.minecraft.client.model.geom.builders.CubeDeformation; import net.minecraft.client.model.geom.EntityModelSet; import net.minecraft.client.model.geom.LayerDefinitions; import net.minecraft.client.model.geom.ModelLayers; @@ -41,7 +42,7 @@ public FancyPlayerModel(ModelPart part, boolean isSlim) { * @return correct model part. */ private static ModelPart getModelPart(EntityModelSet modelSet, boolean isSlim, boolean isBaby) { - LayerDefinition layerDefinition = modelSet.roots.get(isSlim ? ModelLayers.PLAYER_SLIM : ModelLayers.PLAYER); + LayerDefinition layerDefinition = LayerDefinitions.createRoots().get(isSlim ? ModelLayers.PLAYER_SLIM : ModelLayers.PLAYER); if (isBaby) { layerDefinition = layerDefinition.apply(BABY_TRANSFORMER); } @@ -56,7 +57,7 @@ private static ModelPart getModelPart(EntityModelSet modelSet, boolean isSlim, b */ public static ArmorModelSet getBabyArmorModel(boolean isSlim) { return PlayerModel - .createArmorMeshSet(LayerDefinitions.INNER_ARMOR_DEFORMATION, LayerDefinitions.OUTER_ARMOR_DEFORMATION) + .createArmorMeshSet(new CubeDeformation(0.5F), new CubeDeformation(1.0F)) .map(mesh -> new PlayerModel(LayerDefinition.create(mesh, 64, 32).apply(BABY_TRANSFORMER).bakeRoot(), isSlim)); } diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiEntityRendererMixin.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiEntityRendererMixin.java index 76d53e1..cd79bae 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiEntityRendererMixin.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiEntityRendererMixin.java @@ -6,17 +6,18 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.render.pip.GuiEntityRenderer; import net.minecraft.client.gui.render.pip.PictureInPictureRenderer; -import net.minecraft.client.gui.render.state.pip.GuiEntityRenderState; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.entity.EntityRenderDispatcher; import net.minecraft.client.renderer.feature.FeatureRenderDispatcher; -import net.minecraft.client.renderer.state.CameraRenderState; +import net.minecraft.client.renderer.state.gui.pip.GuiEntityRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.joml.Quaternionf; /** * Injects into {@link GuiEntityRenderer} to handle Fancy Entity Widgets. @@ -45,7 +46,7 @@ private GuiEntityRendererMixin(MultiBufferSource.BufferSource bufferSource) { * @param poseStack {@link PoseStack}. * @param ci {@link CallbackInfo}. */ - @Inject(method = "renderToTexture(Lnet/minecraft/client/gui/render/state/pip/GuiEntityRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;)V", at = @At(value = "HEAD"), cancellable = true) + @Inject(method = "renderToTexture(Lnet/minecraft/client/renderer/state/gui/pip/GuiEntityRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;)V", at = @At(value = "HEAD"), cancellable = true) private void onRenderToTexture(GuiEntityRenderState guiState, PoseStack poseStack, CallbackInfo ci) { if (guiState.renderState() instanceof FancyPlayerRenderState renderState) { Minecraft.getInstance().gameRenderer.getLighting().setupFor(Lighting.Entry.ENTITY_IN_UI); @@ -55,7 +56,7 @@ private void onRenderToTexture(GuiEntityRenderState guiState, PoseStack poseStac FeatureRenderDispatcher dispatcher = Minecraft.getInstance().gameRenderer.getFeatureRenderDispatcher(); CameraRenderState camera = new CameraRenderState(); if (guiState.overrideCameraAngle() != null) { - camera.orientation = guiState.overrideCameraAngle(); + camera.orientation = guiState.overrideCameraAngle().conjugate(new Quaternionf()).rotateY((float) Math.PI); } entityRenderDispatcher.submit(renderState, camera, 0, 0, 0, poseStack, dispatcher.getSubmitNodeStorage()); dispatcher.renderAllFeatures(); diff --git a/common/src/main/resources/fancy_entity_renderer.mixins.json b/common/src/main/resources/fancy_entity_renderer.mixins.json index 33e71c3..35c6669 100644 --- a/common/src/main/resources/fancy_entity_renderer.mixins.json +++ b/common/src/main/resources/fancy_entity_renderer.mixins.json @@ -2,7 +2,6 @@ "required": true, "minVersion": "0.8", "package": "${group}.${mod_id}.mixin", - "refmap": "${mod_id}.refmap.json", "compatibilityLevel": "JAVA_${java_version}", "mixins": [], "client": [ @@ -14,4 +13,3 @@ "defaultRequire": 1 } } - diff --git a/fabric/build.gradle b/fabric/build.gradle index 9b7729e..c11b268 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -1,26 +1,25 @@ plugins { id "multiloader-loader" - id "fabric-loom" + id "net.fabricmc.fabric-loom" } +def minecraftClientJar = files(new File(gradle.gradleUserHomeDir, "caches/fabric-loom/${minecraft_version}/minecraft-client.jar")) + dependencies { minecraft "com.mojang:minecraft:$minecraft_version" - mappings loom.layered { - officialMojangMappings() - parchment("org.parchmentmc.data:parchment-$parchment_minecraft:$parchment_version@zip") - } - modImplementation "net.fabricmc:fabric-loader:$fabric_loader_version" - modImplementation "net.fabricmc.fabric-api:fabric-api:$fabric_version" - modCompileOnly "it.crystalnest:prometheus-$name:$minecraft_version-$prometheus_version" + implementation "net.fabricmc:fabric-loader:$fabric_loader_version" + implementation "net.fabricmc.fabric-api:fabric-api:$fabric_version" + compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-fabric:$mixin_extras_version")) + compileOnly "it.crystalnest:prometheus-$name:$prometheus_minecraft_version-$prometheus_version" + compileOnly "it.crystalnest:cobweb-common:$cobweb_minecraft_version-$cobweb_version" } loom { - def aw = project(":common").file("src/main/resources/${mod_id}.accesswidener") - if (aw.exists()) { - accessWidenerPath.set(aw) - } - mixin { - defaultRefmapName.set("${mod_id}.refmap.json") + mods { + "${mod_id}" { + sourceSet sourceSets.main + sourceSet("main", ":common") + } } runs { client { @@ -38,6 +37,50 @@ loom { } } -publisher { - setArtifact(remapJar) +tasks.named("compileJava", JavaCompile) { + classpath += minecraftClientJar + configurations.minecraftLibraries +} + +tasks.named("javadoc", Javadoc) { + classpath += minecraftClientJar + configurations.minecraftLibraries +} + +tasks.register("repairMergedMinecraftJar") { + dependsOn "configureClientLaunch" + + doLast { + def mergedJars = fileTree(rootProject.file(".gradle/loom-cache/minecraftMaven/net/minecraft")).matching { + include "minecraft-merged-*/${minecraft_version}/minecraft-merged-*-${minecraft_version}.jar" + }.files + if (mergedJars.empty) { + throw new GradleException("Could not find Loom merged Minecraft jars for ${minecraft_version}.") + } + + def clientJar = minecraftClientJar.singleFile + mergedJars.each { File mergedJar -> + if (!zipTree(mergedJar).matching { include "**/*.class" }.files.empty) { + return + } + + def mergedJarTempDir = new File(temporaryDir, mergedJar.name.replace(".jar", "")) + delete(mergedJarTempDir) + copy { + from(zipTree(mergedJar)) + from(zipTree(clientJar)) + into(mergedJarTempDir) + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } + ant.zip(destfile: mergedJar) { + fileset(dir: mergedJarTempDir) + } + } + } +} + +tasks.named("runClient") { + dependsOn "repairMergedMinecraftJar" +} + +tasks.matching { it.name == "runClientRenderDoc" }.configureEach { + dependsOn "repairMergedMinecraftJar" } diff --git a/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java b/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java index e0146fe..ee19d33 100644 --- a/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java +++ b/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java @@ -4,19 +4,22 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; import com.mojang.blaze3d.buffers.GpuBufferSlice; +import com.mojang.blaze3d.textures.GpuTexture; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.crystalnest.fancy_entity_renderer.mixin.client.accessor.PictureInPictureRendererAccessor; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.render.GuiRenderer; import net.minecraft.client.gui.render.pip.GuiEntityRenderer; import net.minecraft.client.gui.render.pip.PictureInPictureRenderer; -import net.minecraft.client.gui.render.state.GuiRenderState; -import net.minecraft.client.gui.render.state.pip.GuiEntityRenderState; -import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.feature.FeatureRenderDispatcher; +import net.minecraft.client.renderer.state.gui.GuiRenderState; +import net.minecraft.client.renderer.state.gui.pip.GuiEntityRenderState; +import net.minecraft.client.renderer.state.gui.pip.PictureInPictureRenderState; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -88,7 +91,8 @@ private GuiRendererMixin() {} */ @Unique private static boolean canBeReusedFor(GuiEntityRenderer renderer, GuiEntityRenderState state, int width, int height) { - return renderer.texture == null || (renderer.texture.getWidth(0) == width && renderer.texture.getHeight(0) == height); + GpuTexture texture = ((PictureInPictureRendererAccessor) renderer).fer$getTexture(); + return texture == null || (texture.getWidth(0) == width && texture.getHeight(0) == height); } /** @@ -140,7 +144,7 @@ private void onRender(CallbackInfo ci) { * @param original the original (wrapped) method. * @param i local variable storing the GUI scale at the injection point. */ - @WrapOperation(method = "preparePictureInPicture", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/render/state/GuiRenderState;forEachPictureInPicture(Ljava/util/function/Consumer;)V")) + @WrapOperation(method = "preparePictureInPicture", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/state/gui/GuiRenderState;forEachPictureInPicture(Ljava/util/function/Consumer;)V")) private void redirectForEachPictureInPicture(GuiRenderState instance, Consumer action, Operation original, @Local int i) { // Empty the render states prepared this frame. preparedGuiEntityRenderStates.clear(); @@ -228,7 +232,10 @@ private GuiEntityRenderer getGuiEntityRenderer(GuiEntityRenderState state, int g } } // No suitable renderer was found -> create a new one. - GuiEntityRenderer renderer = new GuiEntityRenderer(guiEntityRenderer.bufferSource, guiEntityRenderer.entityRenderDispatcher); + GuiEntityRenderer renderer = new GuiEntityRenderer( + ((PictureInPictureRendererAccessor) guiEntityRenderer).fer$getBufferSource(), + Minecraft.getInstance().getEntityRenderDispatcher() + ); renderersThisFrame.put(state, renderer); return renderer; } diff --git a/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/accessor/PictureInPictureRendererAccessor.java b/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/accessor/PictureInPictureRendererAccessor.java new file mode 100644 index 0000000..da38d1f --- /dev/null +++ b/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/accessor/PictureInPictureRendererAccessor.java @@ -0,0 +1,18 @@ +package it.crystalnest.fancy_entity_renderer.mixin.client.accessor; + +import com.mojang.blaze3d.textures.GpuTexture; +import net.minecraft.client.gui.render.pip.PictureInPictureRenderer; +import net.minecraft.client.renderer.MultiBufferSource; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PictureInPictureRenderer.class) +public interface PictureInPictureRendererAccessor { + @Accessor("bufferSource") + MultiBufferSource.BufferSource fer$getBufferSource(); + + @Accessor("texture") + @Nullable + GpuTexture fer$getTexture(); +} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index e1ab697..4c153a6 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -20,7 +20,6 @@ "${group}.${mod_id}.ModLoader" ] }, - "accessWidener": "${mod_id}.accesswidener", "mixins": [ "${mod_id}.mixins.json", "${mod_id}.fabric.mixins.json" diff --git a/fabric/src/main/resources/fancy_entity_renderer.fabric.mixins.json b/fabric/src/main/resources/fancy_entity_renderer.fabric.mixins.json index 57dd615..02aadee 100644 --- a/fabric/src/main/resources/fancy_entity_renderer.fabric.mixins.json +++ b/fabric/src/main/resources/fancy_entity_renderer.fabric.mixins.json @@ -2,14 +2,14 @@ "required": true, "minVersion": "0.8", "package": "${group}.${mod_id}.mixin", - "refmap": "${mod_id}.refmap.json", "compatibilityLevel": "JAVA_${java_version}", "mixins": [], "client": [ + "client.accessor.PictureInPictureRendererAccessor", "client.GuiRendererMixin" ], "server": [], "injectors": { "defaultRequire": 1 } -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index f3a804b..4dadb46 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # Every field must be added to the root build.gradle expandProps map. # Common -java_version = 21 +java_version = 25 mod_version = 0.5.4 group = it.crystalnest mod_authors = Crystal Spider @@ -13,26 +13,27 @@ description = I'm a fancy GUI! credits = Keksuccino github_user = crystal-nest curseforge_id = 1173643 -minecraft_version = 1.21.11 +minecraft_version = 26.1.1 # https://minecraft.wiki/w/Pack_format#List_of_data_pack_formats pack_format = 94.1 -# https://parchmentmc.org/docs/getting-started#choose-a-version -parchment_minecraft = 1.21.11 -parchment_version = 2025.12.20 # https://projects.neoforged.net/neoforged/neoform -neo_form_version = 1.21.11-20251209.172050 +neo_form_version = 26.1.1-1 # Fabric (https://fabricmc.net/develop/) -fabric_version = 0.140.0+1.21.11 -fabric_loader_version = 0.18.3 +fabric_version = 0.145.3+26.1.1 +fabric_loader_version = 0.18.6 +fabric_loom_version = 1.15.5 # NeoForge (https://projects.neoforged.net/neoforged/neoforge) -neoforge_version = 21.11.12-beta -neoforge_loader_version_range = [21.11,) +neoforge_version = 26.1.1.6-beta +neoforge_loader_version_range = [26.1,) # Dependencies mixin_extras_version = 0.4.1 prometheus_version = 1.2.5 +prometheus_minecraft_version = 26.1 +cobweb_version = 1.4.0 +cobweb_minecraft_version = 26.1 # Gradle org.gradle.jvmargs = -Xmx4G diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 0000000..d35f707 --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1 @@ +toolchainVersion=25 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4081da..23449a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/neoforge/build.gradle b/neoforge/build.gradle index 322be19..e646804 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -4,19 +4,12 @@ plugins { } dependencies { - compileOnly "it.crystalnest:prometheus-$name:$minecraft_version-$prometheus_version" + compileOnly "it.crystalnest:prometheus-$name:$prometheus_minecraft_version-$prometheus_version" + compileOnly "it.crystalnest:cobweb-common:$cobweb_minecraft_version-$cobweb_version" } neoForge { version = neoforge_version - def at = project(":common").file("src/main/resources/META-INF/accesstransformer.cfg") - if (at.exists()) { - accessTransformers.from(at.absolutePath) - } - parchment { - minecraftVersion = parchment_minecraft - mappingsVersion = parchment_version - } runs { configureEach { systemProperty("neoforge.enabledGameTestNamespaces", mod_id) From 75297febd688dc42139166b11ef1f91e5ffcd545 Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 03:49:39 +0200 Subject: [PATCH 03/15] Port to MC 26.1.1 --- fabric/build.gradle | 41 +---------------------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/fabric/build.gradle b/fabric/build.gradle index c11b268..65bc675 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -27,6 +27,7 @@ loom { setConfigName("Fabric Client") ideConfigGenerated(true) runDir("run/client") + vmArg "-Dfabric.gameJarPath.client=${new File(gradle.gradleUserHomeDir, "caches/fabric-loom/${minecraft_version}/minecraft-client.jar").absolutePath}" } server { server() @@ -44,43 +45,3 @@ tasks.named("compileJava", JavaCompile) { tasks.named("javadoc", Javadoc) { classpath += minecraftClientJar + configurations.minecraftLibraries } - -tasks.register("repairMergedMinecraftJar") { - dependsOn "configureClientLaunch" - - doLast { - def mergedJars = fileTree(rootProject.file(".gradle/loom-cache/minecraftMaven/net/minecraft")).matching { - include "minecraft-merged-*/${minecraft_version}/minecraft-merged-*-${minecraft_version}.jar" - }.files - if (mergedJars.empty) { - throw new GradleException("Could not find Loom merged Minecraft jars for ${minecraft_version}.") - } - - def clientJar = minecraftClientJar.singleFile - mergedJars.each { File mergedJar -> - if (!zipTree(mergedJar).matching { include "**/*.class" }.files.empty) { - return - } - - def mergedJarTempDir = new File(temporaryDir, mergedJar.name.replace(".jar", "")) - delete(mergedJarTempDir) - copy { - from(zipTree(mergedJar)) - from(zipTree(clientJar)) - into(mergedJarTempDir) - duplicatesStrategy = DuplicatesStrategy.INCLUDE - } - ant.zip(destfile: mergedJar) { - fileset(dir: mergedJarTempDir) - } - } - } -} - -tasks.named("runClient") { - dependsOn "repairMergedMinecraftJar" -} - -tasks.matching { it.name == "runClientRenderDoc" }.configureEach { - dependsOn "repairMergedMinecraftJar" -} From 68e0005149226f5d12ed5500e14b37e570c5aacf Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 04:17:32 +0200 Subject: [PATCH 04/15] Port to MC 26.1.1 --- common/build.gradle | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/common/build.gradle b/common/build.gradle index d735c6d..6a98565 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -15,19 +15,6 @@ dependencies { } configurations { - commonJava { - canBeResolved = false - canBeConsumed = true - } - commonResources { - canBeResolved = false - canBeConsumed = true - } -} - -artifacts { - commonJava sourceSets.main.java.sourceDirectories.singleFile - commonResources sourceSets.main.resources.sourceDirectories.singleFile } // Disable publishing tasks where common is not published From 5aebd211317cd9b7f712c197d094da53173e14e9 Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 04:33:06 +0200 Subject: [PATCH 05/15] Port to MC 26.1.1 --- .../client/screen/FerDevTestScreen.java | 175 ++++++++++++++++++ .../mixin/client/TitleScreenMixin.java | 35 ++++ .../fancy_entity_renderer.mixins.json | 3 +- 3 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java create mode 100644 common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/TitleScreenMixin.java diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java new file mode 100644 index 0000000..a0807be --- /dev/null +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java @@ -0,0 +1,175 @@ +package it.crystalnest.fancy_entity_renderer.client.screen; + +import it.crystalnest.fancy_entity_renderer.api.entity.player.FancyPlayerWidget; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.MultiLineTextWidget; +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.entity.animal.parrot.Parrot; +import net.minecraft.world.item.Items; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Small in-dev showcase screen for FER player widgets. + */ +public final class FerDevTestScreen extends Screen { + private static final Component TITLE = Component.literal("FER Dev Test"); + private static final Component DESCRIPTION = Component.literal("Quick in-dev showcase for Fancy Entity Renderer player widgets."); + private static final int PANEL_COLOR = 0xA0101218; + private static final int CARD_COLOR = 0x80303642; + private static final int OUTLINE_COLOR = 0xA06D7B8A; + + private final Screen parent; + private final List cards = new ArrayList<>(); + + public FerDevTestScreen(Screen parent) { + super(TITLE); + this.parent = parent; + } + + @Override + protected void init() { + super.init(); + cards.clear(); + + addRenderableOnly(new StringWidget(0, 16, width, 12, TITLE, font)); + addRenderableOnly(new MultiLineTextWidget(0, 34, DESCRIPTION, font).setMaxWidth(width - 80).setCentered(true)); + + int margin = 28; + int gapX = 28; + int titleY = 72; + int labelHeight = 12; + int firstRowHeight = 188; + int secondRowHeight = 156; + int rowGap = 32; + int columnWidth = Math.max(120, (width - margin * 2 - gapX) / 2); + int leftX = margin; + int rightX = margin + columnWidth + gapX; + int secondRowY = titleY + labelHeight + firstRowHeight + rowGap; + + addCard( + leftX, + titleY, + Math.min(columnWidth, 158), + firstRowHeight, + Component.literal("Heavy Tank"), + widget -> widget + .setName("Heavy Tank") + .setShowName(true) + .setPinName(true) + .setBodyRotation(0, -18, 0) + .setHeadRotation(-6, 12, 0) + .setHeadWearable(Items.NETHERITE_HELMET) + .setChestWearable(Items.NETHERITE_CHESTPLATE) + .setLegsWearable(Items.NETHERITE_LEGGINGS) + .setFeetWearable(Items.NETHERITE_BOOTS) + .setRightHandItem(Items.MACE) + .setLeftHandItem(Items.SHIELD) + ); + + addCard( + rightX, + titleY, + Math.min(columnWidth, 174), + firstRowHeight + 24, + Component.literal("Mouse-Follow Rogue"), + widget -> widget + .setName("Mouse-Follow Rogue") + .setShowName(true) + .setSlim(true) + .setPose(Pose.CROUCHING) + .setBodyFollowsMouse(true) + .setHeadFollowsMouse(true) + .setMoving(true) + .setWalkingSpeed(1.3F) + .setRightHandItem(Items.CROSSBOW) + .setLeftHandItem(Items.TOTEM_OF_UNDYING) + .setHeadWearable(Items.CHAINMAIL_HELMET) + .setChestWearable(Items.ELYTRA) + .setFeetWearable(Items.CHAINMAIL_BOOTS) + ); + + addCard( + leftX, + secondRowY, + Math.min(columnWidth, 126), + secondRowHeight, + Component.literal("Tiny Trouble"), + widget -> widget + .setName("Tiny Trouble") + .setShowName(true) + .setBaby(true) + .setSlim(true) + .setOnFire(true) + .setBodyRotation(-8, 26, 0) + .setHeadRotation(6, -18, 0) + .setRightHandItem(Items.TRIDENT) + .setLeftHandItem(Items.GOLDEN_APPLE) + .setHeadWearable(Items.TURTLE_HELMET) + .setLeftParrot(Parrot.Variant.BLUE) + ); + + addCard( + rightX, + secondRowY, + Math.min(columnWidth, 166), + secondRowHeight + 16, + Component.literal("Spin Demo"), + widget -> widget + .setName("Spin Demo") + .setShowName(true) + .setPose(Pose.SPIN_ATTACK) + .setAttackArm(HumanoidArm.RIGHT) + .setAttackTime(0.35F) + .setMoving(true) + .setMovementSpeed(1.8F) + .setGlowing(0x55FFAA) + .setRightHandItem(Items.DIAMOND_SWORD) + .setLeftHandItem(Items.SHIELD) + .setChestWearable(Items.DIAMOND_CHESTPLATE) + .setLegsWearable(Items.DIAMOND_LEGGINGS) + .setFeetWearable(Items.DIAMOND_BOOTS) + .setRightArmPose(HumanoidModel.ArmPose.ITEM) + ); + + addRenderableWidget( + Button.builder(Component.literal("Back"), button -> onClose()) + .bounds(width - 108, height - 28, 88, 20) + .build() + ); + } + + @Override + public void extractBackground(GuiGraphicsExtractor gfx, int mouseX, int mouseY, float partialTick) { + extractTransparentBackground(gfx); + gfx.fill(12, 12, width - 12, height - 12, PANEL_COLOR); + for (DemoCard card : cards) { + gfx.fill(card.x() - 10, card.labelY() - 8, card.x() + card.width() + 10, card.y() + card.height() + 10, CARD_COLOR); + gfx.outline(card.x() - 10, card.labelY() - 8, card.width() + 20, card.height() + 18 + (card.y() - card.labelY()), OUTLINE_COLOR); + } + } + + @Override + public void onClose() { + if (minecraft != null) { + minecraft.setScreen(parent); + } + } + + private void addCard(int x, int labelY, int width, int height, Component label, Consumer config) { + addRenderableOnly(new StringWidget(x, labelY, width, 12, label, font)); + FancyPlayerWidget widget = addRenderableWidget(new FancyPlayerWidget(x, labelY + 18, width, height)); + config.accept(widget); + cards.add(new DemoCard(x, labelY, labelY + 18, width, height)); + } + + private record DemoCard(int x, int labelY, int y, int width, int height) {} +} diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/TitleScreenMixin.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/TitleScreenMixin.java new file mode 100644 index 0000000..47a7066 --- /dev/null +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/TitleScreenMixin.java @@ -0,0 +1,35 @@ +package it.crystalnest.fancy_entity_renderer.mixin.client; + +import it.crystalnest.fancy_entity_renderer.client.screen.FerDevTestScreen; +import it.crystalnest.fancy_entity_renderer.platform.Services; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Adds a dev-only entry point for FER's manual test screen. + */ +@Mixin(TitleScreen.class) +public abstract class TitleScreenMixin extends Screen { + protected TitleScreenMixin(Component title) { + super(title); + } + + @Inject(method = "init", at = @At("TAIL")) + private void fer$addDevTestButton(CallbackInfo ci) { + if (!Services.PLATFORM.isDevEnv() || minecraft == null) { + return; + } + + addRenderableWidget( + Button.builder(Component.literal("FER Test"), button -> minecraft.setScreen(new FerDevTestScreen((TitleScreen) (Object) this))) + .bounds(20, 20, 88, 20) + .build() + ); + } +} diff --git a/common/src/main/resources/fancy_entity_renderer.mixins.json b/common/src/main/resources/fancy_entity_renderer.mixins.json index 35c6669..518e858 100644 --- a/common/src/main/resources/fancy_entity_renderer.mixins.json +++ b/common/src/main/resources/fancy_entity_renderer.mixins.json @@ -6,7 +6,8 @@ "mixins": [], "client": [ "client.EntityRenderDispatcherMixin", - "client.GuiEntityRendererMixin" + "client.GuiEntityRendererMixin", + "client.TitleScreenMixin" ], "server": [], "injectors": { From 1e6e54a47f0d317f752dd67fe3e8b1d6a318495c Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 04:59:25 +0200 Subject: [PATCH 06/15] Port to MC 26.1.1 --- .../api/entity/player/FancyPlayerWidget.java | 48 ++++++++++++++++--- .../client/screen/FerDevTestScreen.java | 45 ++++++++--------- 2 files changed, 62 insertions(+), 31 deletions(-) diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java index 78f9591..7bc4fbe 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java @@ -20,6 +20,8 @@ import net.minecraft.commands.arguments.item.ItemInput; import net.minecraft.commands.arguments.item.ItemParser; import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.registries.VanillaRegistries; import net.minecraft.network.chat.CommonComponents; import net.minecraft.resources.Identifier; import net.minecraft.util.LightCoordsUtil; @@ -129,6 +131,17 @@ private static Optional handlePlayerCopyError(Throwable error) { return Optional.empty(); } + /** + * Creates an {@link ItemStack} from a registry item in a way that is safe for GUI-only contexts such as the title screen. + * + * @param item item. + * @return {@link ItemStack} to use as wearable. + */ + private static ItemStack createItemStack(Item item) { + GuiItemContext.bootstrap(); + return item.getDefaultInstance(); + } + /** * Parses an item string into an {@link ItemStack} using the given provider. * @@ -138,9 +151,10 @@ private static Optional handlePlayerCopyError(Throwable error) { */ private static ItemStack parseItem(String item, HolderLookup.Provider provider) { try { + GuiItemContext.bootstrap(); ItemInput result = new ItemParser(provider).parse(new StringReader(item)); return result.createItemStack(1); - } catch (CommandSyntaxException e) { + } catch (CommandSyntaxException | RuntimeException e) { Constants.LOGGER.error("Error parsing {}", item, e); return ItemStack.EMPTY; } @@ -929,7 +943,7 @@ public FancyPlayerWidget setLeftHandItem(@Nullable String item, HolderLookup.Pro * @return {@code this}. */ public FancyPlayerWidget setRightHandItem(@Nullable Item item) { - renderState.rightHandItemStack = getNullableItem(item, Item::getDefaultInstance); + renderState.rightHandItemStack = getNullableItem(item, FancyPlayerWidget::createItemStack); return this; } @@ -941,7 +955,7 @@ public FancyPlayerWidget setRightHandItem(@Nullable Item item) { * @return {@code this}. */ public FancyPlayerWidget setLeftHandItem(@Nullable Item item) { - renderState.leftHandItemStack = getNullableItem(item, Item::getDefaultInstance); + renderState.leftHandItemStack = getNullableItem(item, FancyPlayerWidget::createItemStack); return this; } @@ -1029,7 +1043,7 @@ public FancyPlayerWidget setFeetWearable(@Nullable String item, HolderLookup.Pro * @return {@code this}. */ public FancyPlayerWidget setHeadWearable(@Nullable Item item) { - renderState.headEquipment = getNullableItem(item, Item::getDefaultInstance); + renderState.headEquipment = getNullableItem(item, FancyPlayerWidget::createItemStack); return this; } @@ -1041,7 +1055,7 @@ public FancyPlayerWidget setHeadWearable(@Nullable Item item) { * @return {@code this}. */ public FancyPlayerWidget setChestWearable(@Nullable Item item) { - renderState.chestEquipment = getNullableItem(item, Item::getDefaultInstance); + renderState.chestEquipment = getNullableItem(item, FancyPlayerWidget::createItemStack); return this; } @@ -1053,7 +1067,7 @@ public FancyPlayerWidget setChestWearable(@Nullable Item item) { * @return {@code this}. */ public FancyPlayerWidget setLegsWearable(@Nullable Item item) { - renderState.legsEquipment = getNullableItem(item, Item::getDefaultInstance); + renderState.legsEquipment = getNullableItem(item, FancyPlayerWidget::createItemStack); return this; } @@ -1065,7 +1079,7 @@ public FancyPlayerWidget setLegsWearable(@Nullable Item item) { * @return {@code this}. */ public FancyPlayerWidget setFeetWearable(@Nullable Item item) { - renderState.feetEquipment = getNullableItem(item, Item::getDefaultInstance); + renderState.feetEquipment = getNullableItem(item, FancyPlayerWidget::createItemStack); return this; } @@ -1464,4 +1478,24 @@ private OverridableProperties(@NotNull String name) { this.name = name; } } + + /** + * Lazy holder to avoid building lookup data unless item-backed widget state is actually used. + */ + private static final class GuiItemContext { + private static final HolderLookup.Provider PROVIDER = createProvider(); + + private static HolderLookup.Provider createProvider() { + HolderLookup.Provider provider = VanillaRegistries.createLookup(); + BuiltInRegistries.DATA_COMPONENT_INITIALIZERS.build(provider).forEach(pending -> pending.apply()); + return provider; + } + + private static void bootstrap() { + // Trigger static initialization exactly once to bind GUI-safe item components. + PROVIDER.listRegistryKeys(); + } + + private GuiItemContext() {} + } } diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java index a0807be..3259489 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java @@ -40,31 +40,31 @@ protected void init() { super.init(); cards.clear(); - addRenderableOnly(new StringWidget(0, 16, width, 12, TITLE, font)); - addRenderableOnly(new MultiLineTextWidget(0, 34, DESCRIPTION, font).setMaxWidth(width - 80).setCentered(true)); - int margin = 28; int gapX = 28; - int titleY = 72; - int labelHeight = 12; - int firstRowHeight = 188; - int secondRowHeight = 156; - int rowGap = 32; + int contentTop = 92; + int cardSpacing = 52; + int heavyTankHeight = 188; + int rogueHeight = 212; + int tinyTroubleHeight = 156; + int spinDemoHeight = 172; int columnWidth = Math.max(120, (width - margin * 2 - gapX) / 2); int leftX = margin; int rightX = margin + columnWidth + gapX; - int secondRowY = titleY + labelHeight + firstRowHeight + rowGap; + int leftY = contentTop; + int rightY = contentTop; + + addRenderableOnly(new StringWidget(margin, 20, width - margin * 2, 12, TITLE, font)); + addRenderableOnly(new MultiLineTextWidget(margin, 36, DESCRIPTION, font).setMaxWidth(width - margin * 2)); addCard( leftX, - titleY, + leftY, Math.min(columnWidth, 158), - firstRowHeight, + heavyTankHeight, Component.literal("Heavy Tank"), widget -> widget .setName("Heavy Tank") - .setShowName(true) - .setPinName(true) .setBodyRotation(0, -18, 0) .setHeadRotation(-6, 12, 0) .setHeadWearable(Items.NETHERITE_HELMET) @@ -74,16 +74,16 @@ protected void init() { .setRightHandItem(Items.MACE) .setLeftHandItem(Items.SHIELD) ); + leftY += heavyTankHeight + cardSpacing; addCard( rightX, - titleY, + rightY, Math.min(columnWidth, 174), - firstRowHeight + 24, + rogueHeight, Component.literal("Mouse-Follow Rogue"), widget -> widget .setName("Mouse-Follow Rogue") - .setShowName(true) .setSlim(true) .setPose(Pose.CROUCHING) .setBodyFollowsMouse(true) @@ -96,16 +96,16 @@ protected void init() { .setChestWearable(Items.ELYTRA) .setFeetWearable(Items.CHAINMAIL_BOOTS) ); + rightY += rogueHeight + cardSpacing; addCard( leftX, - secondRowY, + leftY, Math.min(columnWidth, 126), - secondRowHeight, + tinyTroubleHeight, Component.literal("Tiny Trouble"), widget -> widget .setName("Tiny Trouble") - .setShowName(true) .setBaby(true) .setSlim(true) .setOnFire(true) @@ -119,21 +119,18 @@ protected void init() { addCard( rightX, - secondRowY, + rightY, Math.min(columnWidth, 166), - secondRowHeight + 16, + spinDemoHeight, Component.literal("Spin Demo"), widget -> widget .setName("Spin Demo") - .setShowName(true) .setPose(Pose.SPIN_ATTACK) .setAttackArm(HumanoidArm.RIGHT) .setAttackTime(0.35F) .setMoving(true) .setMovementSpeed(1.8F) - .setGlowing(0x55FFAA) .setRightHandItem(Items.DIAMOND_SWORD) - .setLeftHandItem(Items.SHIELD) .setChestWearable(Items.DIAMOND_CHESTPLATE) .setLegsWearable(Items.DIAMOND_LEGGINGS) .setFeetWearable(Items.DIAMOND_BOOTS) From ca502fa43fe1666b626b2d5a6e45a8d20a87780e Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 05:00:19 +0200 Subject: [PATCH 07/15] Port to MC 26.1.1 --- .../client/screen/FerDevTestScreen.java | 289 +++++++++--------- 1 file changed, 145 insertions(+), 144 deletions(-) diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java index 3259489..28496bb 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java @@ -21,152 +21,153 @@ * Small in-dev showcase screen for FER player widgets. */ public final class FerDevTestScreen extends Screen { - private static final Component TITLE = Component.literal("FER Dev Test"); - private static final Component DESCRIPTION = Component.literal("Quick in-dev showcase for Fancy Entity Renderer player widgets."); - private static final int PANEL_COLOR = 0xA0101218; - private static final int CARD_COLOR = 0x80303642; - private static final int OUTLINE_COLOR = 0xA06D7B8A; - - private final Screen parent; - private final List cards = new ArrayList<>(); - - public FerDevTestScreen(Screen parent) { - super(TITLE); - this.parent = parent; - } - - @Override - protected void init() { - super.init(); - cards.clear(); - - int margin = 28; - int gapX = 28; - int contentTop = 92; - int cardSpacing = 52; - int heavyTankHeight = 188; - int rogueHeight = 212; - int tinyTroubleHeight = 156; - int spinDemoHeight = 172; - int columnWidth = Math.max(120, (width - margin * 2 - gapX) / 2); - int leftX = margin; - int rightX = margin + columnWidth + gapX; - int leftY = contentTop; - int rightY = contentTop; - - addRenderableOnly(new StringWidget(margin, 20, width - margin * 2, 12, TITLE, font)); - addRenderableOnly(new MultiLineTextWidget(margin, 36, DESCRIPTION, font).setMaxWidth(width - margin * 2)); - - addCard( - leftX, - leftY, - Math.min(columnWidth, 158), - heavyTankHeight, - Component.literal("Heavy Tank"), - widget -> widget - .setName("Heavy Tank") - .setBodyRotation(0, -18, 0) - .setHeadRotation(-6, 12, 0) - .setHeadWearable(Items.NETHERITE_HELMET) - .setChestWearable(Items.NETHERITE_CHESTPLATE) - .setLegsWearable(Items.NETHERITE_LEGGINGS) - .setFeetWearable(Items.NETHERITE_BOOTS) - .setRightHandItem(Items.MACE) - .setLeftHandItem(Items.SHIELD) - ); - leftY += heavyTankHeight + cardSpacing; - - addCard( - rightX, - rightY, - Math.min(columnWidth, 174), - rogueHeight, - Component.literal("Mouse-Follow Rogue"), - widget -> widget - .setName("Mouse-Follow Rogue") - .setSlim(true) - .setPose(Pose.CROUCHING) - .setBodyFollowsMouse(true) - .setHeadFollowsMouse(true) - .setMoving(true) - .setWalkingSpeed(1.3F) - .setRightHandItem(Items.CROSSBOW) - .setLeftHandItem(Items.TOTEM_OF_UNDYING) - .setHeadWearable(Items.CHAINMAIL_HELMET) - .setChestWearable(Items.ELYTRA) - .setFeetWearable(Items.CHAINMAIL_BOOTS) - ); - rightY += rogueHeight + cardSpacing; - - addCard( - leftX, - leftY, - Math.min(columnWidth, 126), - tinyTroubleHeight, - Component.literal("Tiny Trouble"), - widget -> widget - .setName("Tiny Trouble") - .setBaby(true) - .setSlim(true) - .setOnFire(true) - .setBodyRotation(-8, 26, 0) - .setHeadRotation(6, -18, 0) - .setRightHandItem(Items.TRIDENT) - .setLeftHandItem(Items.GOLDEN_APPLE) - .setHeadWearable(Items.TURTLE_HELMET) - .setLeftParrot(Parrot.Variant.BLUE) - ); - - addCard( - rightX, - rightY, - Math.min(columnWidth, 166), - spinDemoHeight, - Component.literal("Spin Demo"), - widget -> widget - .setName("Spin Demo") - .setPose(Pose.SPIN_ATTACK) - .setAttackArm(HumanoidArm.RIGHT) - .setAttackTime(0.35F) - .setMoving(true) - .setMovementSpeed(1.8F) - .setRightHandItem(Items.DIAMOND_SWORD) - .setChestWearable(Items.DIAMOND_CHESTPLATE) - .setLegsWearable(Items.DIAMOND_LEGGINGS) - .setFeetWearable(Items.DIAMOND_BOOTS) - .setRightArmPose(HumanoidModel.ArmPose.ITEM) - ); - - addRenderableWidget( - Button.builder(Component.literal("Back"), button -> onClose()) - .bounds(width - 108, height - 28, 88, 20) - .build() - ); - } - - @Override - public void extractBackground(GuiGraphicsExtractor gfx, int mouseX, int mouseY, float partialTick) { - extractTransparentBackground(gfx); - gfx.fill(12, 12, width - 12, height - 12, PANEL_COLOR); - for (DemoCard card : cards) { - gfx.fill(card.x() - 10, card.labelY() - 8, card.x() + card.width() + 10, card.y() + card.height() + 10, CARD_COLOR); - gfx.outline(card.x() - 10, card.labelY() - 8, card.width() + 20, card.height() + 18 + (card.y() - card.labelY()), OUTLINE_COLOR); + private static final Component TITLE = Component.literal("FER Dev Test"); + private static final Component DESCRIPTION = Component.literal("Quick in-dev showcase for Fancy Entity Renderer player widgets."); + private static final int PANEL_COLOR = 0xA0101218; + private static final int CARD_COLOR = 0x80303642; + private static final int OUTLINE_COLOR = 0xA06D7B8A; + + private final Screen parent; + private final List cards = new ArrayList<>(); + + public FerDevTestScreen(Screen parent) { + super(TITLE); + this.parent = parent; } - } - @Override - public void onClose() { - if (minecraft != null) { - minecraft.setScreen(parent); + @Override + protected void init() { + super.init(); + cards.clear(); + + int margin = 28; + int gapX = 28; + int contentTop = 92; + int cardSpacing = 52; + int heavyTankHeight = 188; + int rogueHeight = 212; + int tinyTroubleHeight = 156; + int spinDemoHeight = 172; + int columnWidth = Math.max(120, (width - margin * 2 - gapX) / 2); + int leftX = margin; + int rightX = margin + columnWidth + gapX; + int leftY = contentTop; + int rightY = contentTop; + + addRenderableOnly(new StringWidget(margin, 20, width - margin * 2, 12, TITLE, font)); + addRenderableOnly(new MultiLineTextWidget(margin, 36, DESCRIPTION, font).setMaxWidth(width - margin * 2)); + + addCard( + leftX, + leftY, + Math.min(columnWidth, 158), + heavyTankHeight, + Component.literal("Heavy Tank"), + widget -> widget + .setName("Heavy Tank") + .setBodyRotation(0, -18, 0) + .setHeadRotation(-6, 12, 0) + .setHeadWearable(Items.NETHERITE_HELMET) + .setChestWearable(Items.NETHERITE_CHESTPLATE) + .setLegsWearable(Items.NETHERITE_LEGGINGS) + .setFeetWearable(Items.NETHERITE_BOOTS) + .setRightHandItem(Items.MACE) + .setLeftHandItem(Items.SHIELD) + ); + leftY += heavyTankHeight + cardSpacing; + + addCard( + rightX, + rightY, + Math.min(columnWidth, 174), + rogueHeight, + Component.literal("Mouse-Follow Rogue"), + widget -> widget + .setName("Mouse-Follow Rogue") + .setSlim(true) + .setPose(Pose.CROUCHING) + .setBodyFollowsMouse(true) + .setHeadFollowsMouse(true) + .setMoving(true) + .setWalkingSpeed(1.3F) + .setRightHandItem(Items.CROSSBOW) + .setLeftHandItem(Items.TOTEM_OF_UNDYING) + .setHeadWearable(Items.CHAINMAIL_HELMET) + .setChestWearable(Items.ELYTRA) + .setFeetWearable(Items.CHAINMAIL_BOOTS) + ); + rightY += rogueHeight + cardSpacing; + + addCard( + leftX, + leftY, + Math.min(columnWidth, 126), + tinyTroubleHeight, + Component.literal("Tiny Trouble"), + widget -> widget + .setName("Tiny Trouble") + .setBaby(true) + .setSlim(true) + .setOnFire(true) + .setBodyRotation(-8, 26, 0) + .setHeadRotation(6, -18, 0) + .setRightHandItem(Items.TRIDENT) + .setLeftHandItem(Items.GOLDEN_APPLE) + .setHeadWearable(Items.TURTLE_HELMET) + .setLeftParrot(Parrot.Variant.BLUE) + ); + + addCard( + rightX, + rightY, + Math.min(columnWidth, 166), + spinDemoHeight, + Component.literal("Spin Demo"), + widget -> widget + .setName("Spin Demo") + .setPose(Pose.SPIN_ATTACK) + .setAttackArm(HumanoidArm.RIGHT) + .setAttackTime(0.35F) + .setMoving(true) + .setMovementSpeed(1.8F) + .setRightHandItem(Items.DIAMOND_SWORD) + .setChestWearable(Items.DIAMOND_CHESTPLATE) + .setLegsWearable(Items.DIAMOND_LEGGINGS) + .setFeetWearable(Items.DIAMOND_BOOTS) + .setRightArmPose(HumanoidModel.ArmPose.ITEM) + ); + + addRenderableWidget( + Button.builder(Component.literal("Back"), button -> onClose()) + .bounds(width - 108, height - 28, 88, 20) + .build() + ); } - } - private void addCard(int x, int labelY, int width, int height, Component label, Consumer config) { - addRenderableOnly(new StringWidget(x, labelY, width, 12, label, font)); - FancyPlayerWidget widget = addRenderableWidget(new FancyPlayerWidget(x, labelY + 18, width, height)); - config.accept(widget); - cards.add(new DemoCard(x, labelY, labelY + 18, width, height)); - } + @Override + public void extractBackground(GuiGraphicsExtractor gfx, int mouseX, int mouseY, float partialTick) { + extractTransparentBackground(gfx); + gfx.fill(12, 12, width - 12, height - 12, PANEL_COLOR); + for (DemoCard card : cards) { + gfx.fill(card.x() - 10, card.labelY() - 8, card.x() + card.width() + 10, card.y() + card.height() + 10, CARD_COLOR); + gfx.outline(card.x() - 10, card.labelY() - 8, card.width() + 20, card.height() + 18 + (card.y() - card.labelY()), OUTLINE_COLOR); + } + } + + @Override + public void onClose() { + if (minecraft != null) { + minecraft.setScreen(parent); + } + } + + private void addCard(int x, int labelY, int width, int height, Component label, Consumer config) { + addRenderableOnly(new StringWidget(x, labelY, width, 12, label, font)); + FancyPlayerWidget widget = addRenderableWidget(new FancyPlayerWidget(x, labelY + 18, width, height)); + config.accept(widget); + cards.add(new DemoCard(x, labelY, labelY + 18, width, height)); + } + + private record DemoCard(int x, int labelY, int y, int width, int height) {} - private record DemoCard(int x, int labelY, int y, int width, int height) {} -} +} \ No newline at end of file From a8091b6c80b6deee814f902ab4ce0c6b4bfbd8d9 Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 05:04:18 +0200 Subject: [PATCH 08/15] Port to MC 26.1.1 --- .../it/crystalnest/fancy_entity_renderer/ModLoader.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/neoforge/src/main/java/it/crystalnest/fancy_entity_renderer/ModLoader.java b/neoforge/src/main/java/it/crystalnest/fancy_entity_renderer/ModLoader.java index 02a584e..83e4966 100644 --- a/neoforge/src/main/java/it/crystalnest/fancy_entity_renderer/ModLoader.java +++ b/neoforge/src/main/java/it/crystalnest/fancy_entity_renderer/ModLoader.java @@ -1,5 +1,6 @@ package it.crystalnest.fancy_entity_renderer; +import net.minecraft.client.Minecraft; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; import org.jetbrains.annotations.ApiStatus; @@ -10,10 +11,14 @@ @ApiStatus.Internal @Mod(Constants.MOD_ID) public final class ModLoader { + /** * Mod initialization. * * @param bus Event bus. */ - public ModLoader(IEventBus bus) {} + public ModLoader(IEventBus bus) { + Minecraft.getInstance().getWindow(); + } + } From 2e13fc6edac04c032305d5d1a958534172f4403d Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 05:04:31 +0200 Subject: [PATCH 09/15] Port to MC 26.1.1 --- .../java/it/crystalnest/fancy_entity_renderer/ModLoader.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/neoforge/src/main/java/it/crystalnest/fancy_entity_renderer/ModLoader.java b/neoforge/src/main/java/it/crystalnest/fancy_entity_renderer/ModLoader.java index 83e4966..29ed894 100644 --- a/neoforge/src/main/java/it/crystalnest/fancy_entity_renderer/ModLoader.java +++ b/neoforge/src/main/java/it/crystalnest/fancy_entity_renderer/ModLoader.java @@ -1,6 +1,5 @@ package it.crystalnest.fancy_entity_renderer; -import net.minecraft.client.Minecraft; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; import org.jetbrains.annotations.ApiStatus; @@ -18,7 +17,6 @@ public final class ModLoader { * @param bus Event bus. */ public ModLoader(IEventBus bus) { - Minecraft.getInstance().getWindow(); } } From b37c5458d782b0b74a7fe3b09bce2d7ded48881f Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 05:20:18 +0200 Subject: [PATCH 10/15] Port to MC 26.1.1 --- .../src/main/groovy/multiloader-common.gradle | 1 + .../mixin/client/GuiRendererMixin.java | 386 +++++++++--------- 2 files changed, 194 insertions(+), 193 deletions(-) diff --git a/buildSrc/src/main/groovy/multiloader-common.gradle b/buildSrc/src/main/groovy/multiloader-common.gradle index 1ce43d1..963d278 100644 --- a/buildSrc/src/main/groovy/multiloader-common.gradle +++ b/buildSrc/src/main/groovy/multiloader-common.gradle @@ -1,4 +1,5 @@ plugins { + id "idea" id "java-library" id "maven-publish" id "com.hypherionmc.modutils.modpublisher" diff --git a/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java b/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java index ee19d33..0bbb89b 100644 --- a/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java +++ b/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java @@ -40,203 +40,203 @@ */ @Mixin(GuiRenderer.class) public abstract class GuiRendererMixin { - /** - * Set of already prepared {@code GuiEntityRenderState}s in the current frame. - */ - @Unique - private final Set preparedGuiEntityRenderStates = new ReferenceOpenHashSet<>(); - - /** - * Shadowed {@link GuiRenderer#renderState}. - */ - @Final - @Shadow - GuiRenderState renderState; - - /** - * Reference to the main {@link GuiEntityRenderer}. - */ - @Unique - GuiEntityRenderer guiEntityRenderer; - - /** - * {@code GuiEntityRenderer}s used in the last frame which could be reused in the current frame. - */ - @Unique - private Object2ObjectMap renderersLastFrame = new Object2ObjectOpenHashMap<>(); - - /** - * {@code GuiEntityRenderer}s already used in the current frame which could be reused in the next one. - */ - @Unique - private Object2ObjectMap renderersThisFrame = new Object2ObjectOpenHashMap<>(); - - /** - * Shadowed {@link GuiRenderer#pictureInPictureRenderers}. - */ - @Final - @Shadow - private Map, PictureInPictureRenderer> pictureInPictureRenderers; - - private GuiRendererMixin() {} - - /** - * Checks whether the given renderer can be reused for the given state, texture width, and texture height. - * - * @param renderer {@link GuiEntityRenderer}. - * @param state {@link GuiEntityRenderState}. - * @param width texture width. - * @param height texture height. - * @return Whether the renderer can be reused. - */ - @Unique - private static boolean canBeReusedFor(GuiEntityRenderer renderer, GuiEntityRenderState state, int width, int height) { - GpuTexture texture = ((PictureInPictureRendererAccessor) renderer).fer$getTexture(); - return texture == null || (texture.getWidth(0) == width && texture.getHeight(0) == height); - } - - /** - * Shadowed {@link GuiRenderer#preparePictureInPictureState(PictureInPictureRenderState, int)}. - * - * @param state PiP state. - * @param guiScale GUI scale. - * @param state type. - */ - @Shadow - protected abstract void preparePictureInPictureState(T state, int guiScale); - - /** - * Injects at the end of the constructor to save the reference to the main {@link GuiEntityRenderer}. - * - * @param renderState {@link GuiRenderState}. - * @param bufferSource buffer source. - * @param submitNodeCollector submit node collector. - * @param featureRenderDispatcher render dispatcher. - * @param renderers list of PiP renderers. - * @param ci {@link CallbackInfo}. - */ - @Inject(method = "", at = @At(value = "TAIL")) - private void onInit(GuiRenderState renderState, MultiBufferSource.BufferSource bufferSource, SubmitNodeCollector submitNodeCollector, FeatureRenderDispatcher featureRenderDispatcher, List> renderers, CallbackInfo ci) { - guiEntityRenderer = (GuiEntityRenderer) pictureInPictureRenderers.get(GuiEntityRenderState.class); - } - - /** - * Injects at the end of the method {@link GuiRenderer#render(GpuBufferSlice)}.
- * Clears the renderers from last frame and then swaps the per-frame renderer lists. - * - * @param ci {@link CallbackInfo}. - */ - @Inject(method = "render", at = @At(value = "TAIL")) - private void onRender(CallbackInfo ci) { - renderersLastFrame.values().forEach(PictureInPictureRenderer::close); - renderersLastFrame.clear(); - Object2ObjectMap tmp = renderersLastFrame; - renderersLastFrame = renderersThisFrame; - renderersThisFrame = tmp; - } - - /** - * Wraps the call to {@link GuiRenderState#forEachPictureInPicture(Consumer)} inside the method {@link GuiRenderer#preparePictureInPicture()}.
- * Prepares multiple {@link GuiEntityRenderState}s per frame with a double pass. - * - * @param instance {@link GuiRenderState} invoking (owning) the wrapped method. - * @param action the consumer passed to the wrapped method. - * @param original the original (wrapped) method. - * @param i local variable storing the GUI scale at the injection point. - */ - @WrapOperation(method = "preparePictureInPicture", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/state/gui/GuiRenderState;forEachPictureInPicture(Ljava/util/function/Consumer;)V")) - private void redirectForEachPictureInPicture(GuiRenderState instance, Consumer action, Operation original, @Local int i) { - // Empty the render states prepared this frame. - preparedGuiEntityRenderStates.clear(); - // Make the first prepare pass for GuiEntityRenderStates and prepare all others. - original.call(instance, (Consumer) state -> { - if (state instanceof GuiEntityRenderState guiEntityRenderState) { - if (prepareGuiEntityRenderState(guiEntityRenderState, i, true)) { - preparedGuiEntityRenderStates.add(guiEntityRenderState); + /** + * Set of already prepared {@code GuiEntityRenderState}s in the current frame. + */ + @Unique + private final Set preparedGuiEntityRenderStates = new ReferenceOpenHashSet<>(); + + /** + * Shadowed {@link GuiRenderer#renderState}. + */ + @Final + @Shadow + GuiRenderState renderState; + + /** + * Reference to the main {@link GuiEntityRenderer}. + */ + @Unique + GuiEntityRenderer guiEntityRenderer; + + /** + * {@code GuiEntityRenderer}s used in the last frame which could be reused in the current frame. + */ + @Unique + private Object2ObjectMap renderersLastFrame = new Object2ObjectOpenHashMap<>(); + + /** + * {@code GuiEntityRenderer}s already used in the current frame which could be reused in the next one. + */ + @Unique + private Object2ObjectMap renderersThisFrame = new Object2ObjectOpenHashMap<>(); + + /** + * Shadowed {@link GuiRenderer#pictureInPictureRenderers}. + */ + @Final + @Shadow + private Map, PictureInPictureRenderer> pictureInPictureRenderers; + + private GuiRendererMixin() {} + + /** + * Checks whether the given renderer can be reused for the given state, texture width, and texture height. + * + * @param renderer {@link GuiEntityRenderer}. + * @param state {@link GuiEntityRenderState}. + * @param width texture width. + * @param height texture height. + * @return Whether the renderer can be reused. + */ + @Unique + private static boolean canBeReusedFor(GuiEntityRenderer renderer, GuiEntityRenderState state, int width, int height) { + GpuTexture texture = ((PictureInPictureRendererAccessor) renderer).fer$getTexture(); + return texture == null || (texture.getWidth(0) == width && texture.getHeight(0) == height); + } + + /** + * Shadowed {@link GuiRenderer#preparePictureInPictureState(PictureInPictureRenderState, int)}. + * + * @param state PiP state. + * @param guiScale GUI scale. + * @param state type. + */ + @Shadow + protected abstract void preparePictureInPictureState(T state, int guiScale); + + /** + * Injects at the end of the constructor to save the reference to the main {@link GuiEntityRenderer}. + * + * @param renderState {@link GuiRenderState}. + * @param bufferSource buffer source. + * @param submitNodeCollector submit node collector. + * @param featureRenderDispatcher render dispatcher. + * @param renderers list of PiP renderers. + * @param ci {@link CallbackInfo}. + */ + @Inject(method = "", at = @At(value = "TAIL")) + private void onInit(GuiRenderState renderState, MultiBufferSource.BufferSource bufferSource, SubmitNodeCollector submitNodeCollector, FeatureRenderDispatcher featureRenderDispatcher, List> renderers, CallbackInfo ci) { + guiEntityRenderer = (GuiEntityRenderer) pictureInPictureRenderers.get(GuiEntityRenderState.class); + } + + /** + * Injects at the end of the method {@link GuiRenderer#render(GpuBufferSlice)}.
+ * Clears the renderers from last frame and then swaps the per-frame renderer lists. + * + * @param ci {@link CallbackInfo}. + */ + @Inject(method = "render", at = @At(value = "TAIL")) + private void onRender(CallbackInfo ci) { + renderersLastFrame.values().forEach(PictureInPictureRenderer::close); + renderersLastFrame.clear(); + Object2ObjectMap tmp = renderersLastFrame; + renderersLastFrame = renderersThisFrame; + renderersThisFrame = tmp; + } + + /** + * Wraps the call to {@link GuiRenderState#forEachPictureInPicture(Consumer)} inside the method {@link GuiRenderer#preparePictureInPicture()}.
+ * Prepares multiple {@link GuiEntityRenderState}s per frame with a double pass. + * + * @param instance {@link GuiRenderState} invoking (owning) the wrapped method. + * @param action the consumer passed to the wrapped method. + * @param original the original (wrapped) method. + * @param i local variable storing the GUI scale at the injection point. + */ + @WrapOperation(method = "preparePictureInPicture", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/state/gui/GuiRenderState;forEachPictureInPicture(Ljava/util/function/Consumer;)V")) + private void redirectForEachPictureInPicture(GuiRenderState instance, Consumer action, Operation original, @Local int i) { + // Empty the render states prepared this frame. + preparedGuiEntityRenderStates.clear(); + // Make the first prepare pass for GuiEntityRenderStates and prepare all others. + original.call(instance, (Consumer) state -> { + if (state instanceof GuiEntityRenderState guiEntityRenderState) { + if (prepareGuiEntityRenderState(guiEntityRenderState, i, true)) { + preparedGuiEntityRenderStates.add(guiEntityRenderState); + } + } else { + action.accept(state); + } + }); + // Make the second prepare pass for GuiEntityRenderStates. + original.call(instance, (Consumer) state -> { + if (state instanceof GuiEntityRenderState guiEntityRenderState && preparedGuiEntityRenderStates.add(guiEntityRenderState)) { + prepareGuiEntityRenderState(guiEntityRenderState, i, false); + } + }); + // Empty the render states prepared this frame. + preparedGuiEntityRenderStates.clear(); + } + + /** + * Tries to prepare the given state. + * + * @param state {@link GuiEntityRenderState}. + * @param guiScale GUI scale. + * @param first whether it's the first or second prepare pass. + * @return whether the state was prepared. + */ + @Unique + private boolean prepareGuiEntityRenderState(GuiEntityRenderState state, int guiScale, boolean first) { + GuiEntityRenderer renderer = getGuiEntityRenderer(state, guiScale, first); + if (renderer != null) { + renderer.prepare(state, renderState, guiScale); + return true; } - } else { - action.accept(state); - } - }); - // Make the second prepare pass for GuiEntityRenderStates. - original.call(instance, (Consumer) state -> { - if (state instanceof GuiEntityRenderState guiEntityRenderState && preparedGuiEntityRenderStates.add(guiEntityRenderState)) { - prepareGuiEntityRenderState(guiEntityRenderState, i, false); - } - }); - // Empty the render states prepared this frame. - preparedGuiEntityRenderStates.clear(); - } - - /** - * Tries to prepare the given state. - * - * @param state {@link GuiEntityRenderState}. - * @param guiScale GUI scale. - * @param first whether it's the first or second prepare pass. - * @return whether the state was prepared. - */ - @Unique - private boolean prepareGuiEntityRenderState(GuiEntityRenderState state, int guiScale, boolean first) { - GuiEntityRenderer renderer = getGuiEntityRenderer(state, guiScale, first); - if (renderer != null) { - renderer.prepare(state, renderState, guiScale); - return true; + return false; } - return false; - } - - /** - * Injects in the method {@link GuiRenderer#close()} after the first call to {@link Collection#forEach(Consumer)} that closes all PiP renderers.
- * Also closes all renderers saved in {@link GuiRendererMixin#renderersLastFrame} and {@link GuiRendererMixin#renderersLastFrame}. - * - * @param ci {@link CallbackInfo}. - */ - @Inject(method = "close", at = @At(value = "INVOKE", target = "Ljava/util/Collection;forEach(Ljava/util/function/Consumer;)V", ordinal = 0)) - private void onClose(CallbackInfo ci) { - renderersThisFrame.values().forEach(PictureInPictureRenderer::close); - renderersLastFrame.values().forEach(PictureInPictureRenderer::close); - } - - /** - * Returns the most suitable {@link GuiEntityRenderer} for the given state. - * - * @param state {@link GuiEntityRenderState}. - * @param guiScale GUI scale. - * @param first whether it's the first or second prepare pass. - * @return the most suitable {@link GuiEntityRenderer}. - */ - @Unique - @Nullable - private GuiEntityRenderer getGuiEntityRenderer(GuiEntityRenderState state, int guiScale, boolean first) { - int width = (state.x1() - state.x0()) * guiScale; - int height = (state.y1() - state.y0()) * guiScale; - // First prepare pass: try to reuse existing renderers by state equality. - if (first) { - GuiEntityRenderer renderer = renderersLastFrame.get(state); - if (renderer != null && canBeReusedFor(renderer, state, width, height)) { - renderersLastFrame.remove(state); - renderersThisFrame.put(state, renderer); - return renderer; - } - return null; + + /** + * Injects in the method {@link GuiRenderer#close()} after the first call to {@link Collection#forEach(Consumer)} that closes all PiP renderers.
+ * Also closes all renderers saved in {@link GuiRendererMixin#renderersLastFrame} and {@link GuiRendererMixin#renderersLastFrame}. + * + * @param ci {@link CallbackInfo}. + */ + @Inject(method = "close", at = @At(value = "INVOKE", target = "Ljava/util/Collection;forEach(Ljava/util/function/Consumer;)V", ordinal = 0)) + private void onClose(CallbackInfo ci) { + renderersThisFrame.values().forEach(PictureInPictureRenderer::close); + renderersLastFrame.values().forEach(PictureInPictureRenderer::close); } - // Second prepare pass: try to find a renderer of matching texture size. - ObjectIterator it = renderersLastFrame.values().iterator(); - while (it.hasNext()) { - GuiEntityRenderer renderer = it.next(); - if (canBeReusedFor(renderer, state, width, height)) { - it.remove(); + + /** + * Returns the most suitable {@link GuiEntityRenderer} for the given state. + * + * @param state {@link GuiEntityRenderState}. + * @param guiScale GUI scale. + * @param first whether it's the first or second prepare pass. + * @return the most suitable {@link GuiEntityRenderer}. + */ + @Unique + @Nullable + private GuiEntityRenderer getGuiEntityRenderer(GuiEntityRenderState state, int guiScale, boolean first) { + int width = (state.x1() - state.x0()) * guiScale; + int height = (state.y1() - state.y0()) * guiScale; + // First prepare pass: try to reuse existing renderers by state equality. + if (first) { + GuiEntityRenderer renderer = renderersLastFrame.get(state); + if (renderer != null && canBeReusedFor(renderer, state, width, height)) { + renderersLastFrame.remove(state); + renderersThisFrame.put(state, renderer); + return renderer; + } + return null; + } + // Second prepare pass: try to find a renderer of matching texture size. + ObjectIterator it = renderersLastFrame.values().iterator(); + while (it.hasNext()) { + GuiEntityRenderer renderer = it.next(); + if (canBeReusedFor(renderer, state, width, height)) { + it.remove(); + renderersThisFrame.put(state, renderer); + return renderer; + } + } + // No suitable renderer was found -> create a new one. + GuiEntityRenderer renderer = new GuiEntityRenderer( + ((PictureInPictureRendererAccessor) guiEntityRenderer).fer$getBufferSource(), + Minecraft.getInstance().getEntityRenderDispatcher() + ); renderersThisFrame.put(state, renderer); return renderer; - } } - // No suitable renderer was found -> create a new one. - GuiEntityRenderer renderer = new GuiEntityRenderer( - ((PictureInPictureRendererAccessor) guiEntityRenderer).fer$getBufferSource(), - Minecraft.getInstance().getEntityRenderDispatcher() - ); - renderersThisFrame.put(state, renderer); - return renderer; - } } From 43c7669684134b31998316940a6507bd9e1fd2d2 Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 05:21:41 +0200 Subject: [PATCH 11/15] Port to MC 26.1.1 --- .../fancy_entity_renderer/mixin/client/GuiRendererMixin.java | 1 + 1 file changed, 1 insertion(+) diff --git a/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java b/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java index 0bbb89b..1317730 100644 --- a/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java +++ b/fabric/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/GuiRendererMixin.java @@ -40,6 +40,7 @@ */ @Mixin(GuiRenderer.class) public abstract class GuiRendererMixin { + /** * Set of already prepared {@code GuiEntityRenderState}s in the current frame. */ From 0baebfe650bd926d03acbd451a1d9f20059df311 Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 05:24:27 +0200 Subject: [PATCH 12/15] Port to MC 26.1.1 --- .../mixin/client/TitleScreenMixin.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/TitleScreenMixin.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/TitleScreenMixin.java index 47a7066..78467e4 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/TitleScreenMixin.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/client/TitleScreenMixin.java @@ -16,20 +16,22 @@ */ @Mixin(TitleScreen.class) public abstract class TitleScreenMixin extends Screen { - protected TitleScreenMixin(Component title) { - super(title); - } - @Inject(method = "init", at = @At("TAIL")) - private void fer$addDevTestButton(CallbackInfo ci) { - if (!Services.PLATFORM.isDevEnv() || minecraft == null) { - return; + protected TitleScreenMixin(Component title) { + super(title); + } + + @Inject(method = "init", at = @At("TAIL")) + private void fer$addDevTestButton(CallbackInfo ci) { + if (!Services.PLATFORM.isDevEnv() || minecraft == null) { + return; + } + + addRenderableWidget( + Button.builder(Component.literal("FER Test"), button -> minecraft.setScreen(new FerDevTestScreen((TitleScreen) (Object) this))) + .bounds(20, 20, 88, 20) + .build() + ); } - addRenderableWidget( - Button.builder(Component.literal("FER Test"), button -> minecraft.setScreen(new FerDevTestScreen((TitleScreen) (Object) this))) - .bounds(20, 20, 88, 20) - .build() - ); - } } From 8c1c4a4e2e2bf2f210003f6e3b4188bff8251f76 Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 05:47:00 +0200 Subject: [PATCH 13/15] Port to MC 26.1.1 --- .../entity/player/FancyPlayerRenderer.java | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerRenderer.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerRenderer.java index 07da606..f549646 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerRenderer.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerRenderer.java @@ -100,30 +100,8 @@ protected void submitNameDisplay(@NotNull AvatarRenderState renderState, @NotNul poseStack.rotateAround(Axis.ZP.rotationDegrees(Math.min(Mth.sqrt((state.deathTime - 1) / 20F * 1.6F), 1) * 90), 0, offsetY, 0); } float x = -minecraft.font.width(text) / 2F; - submitNodeCollector.submitText( - poseStack, - x, - offset, - text, - false, - Font.DisplayMode.SEE_THROUGH, - state.lightCoords, - -2130706433, - (int) (minecraft.options.getBackgroundOpacity(0.25F) * 255F) << 24, - 0 - ); - submitNodeCollector.submitText( - poseStack, - x, - offset, - text, - false, - Font.DisplayMode.NORMAL, - LightCoordsUtil.lightCoordsWithEmission(state.lightCoords, 2), - state.isDiscrete ? -2130706433 : -1, - 0, - 0 - ); + submitNodeCollector.submitText(poseStack, x, offset, text, false, Font.DisplayMode.SEE_THROUGH, state.lightCoords, -2130706433, (int) (minecraft.options.getBackgroundOpacity(0.25F) * 255F) << 24, 0); + submitNodeCollector.submitText(poseStack, x, offset, text, false, Font.DisplayMode.NORMAL, LightCoordsUtil.lightCoordsWithEmission(state.lightCoords, 2), state.isDiscrete ? -2130706433 : -1, 0, 0); poseStack.popPose(); } else { super.submitNameDisplay(renderState, poseStack, submitNodeCollector, camera); From 1af8d3b91b32cc3ac16e17cff6d7772a756f658d Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Sun, 5 Apr 2026 18:50:07 +0200 Subject: [PATCH 14/15] Port to MC 26.1.1 --- .../api/entity/player/FancyPlayerWidget.java | 63 ++++++++++++++++++- .../accessor/HolderSetNamedAccessor.java | 21 +++++++ .../fancy_entity_renderer.mixins.json | 4 +- 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/accessor/HolderSetNamedAccessor.java diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java index 7bc4fbe..adaaa11 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/api/entity/player/FancyPlayerWidget.java @@ -8,6 +8,7 @@ import it.crystalnest.fancy_entity_renderer.api.entity.RenderMode; import it.crystalnest.fancy_entity_renderer.api.entity.player.state.FancyPlayerRenderState; import it.crystalnest.fancy_entity_renderer.compat.Prometheus; +import it.crystalnest.fancy_entity_renderer.mixin.accessor.HolderSetNamedAccessor; import it.crystalnest.fancy_entity_renderer.platform.Services; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; @@ -20,10 +21,15 @@ import net.minecraft.commands.arguments.item.ItemInput; import net.minecraft.commands.arguments.item.ItemParser; import net.minecraft.core.HolderLookup; +import net.minecraft.core.HolderSet; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.data.registries.VanillaRegistries; import net.minecraft.network.chat.CommonComponents; import net.minecraft.resources.Identifier; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.resources.MultiPackResourceManager; +import net.minecraft.tags.TagLoader; +import net.minecraft.tags.TagKey; import net.minecraft.util.LightCoordsUtil; import net.minecraft.world.entity.Avatar; import net.minecraft.world.entity.Entity; @@ -43,10 +49,12 @@ import org.joml.Vector3f; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.function.Function; +import java.util.stream.Stream; /** * Custom player widget. @@ -1486,11 +1494,64 @@ private static final class GuiItemContext { private static final HolderLookup.Provider PROVIDER = createProvider(); private static HolderLookup.Provider createProvider() { - HolderLookup.Provider provider = VanillaRegistries.createLookup(); + HolderLookup.Provider baseProvider = VanillaRegistries.createLookup(); + HolderLookup.Provider provider; + try (MultiPackResourceManager dataResources = new MultiPackResourceManager( + PackType.SERVER_DATA, + Minecraft.getInstance().getResourcePackRepository().openAllSelected() + )) { + provider = HolderLookup.Provider.create(baseProvider.listRegistries().map(lookup -> withLoadedTags(dataResources, lookup))); + } BuiltInRegistries.DATA_COMPONENT_INITIALIZERS.build(provider).forEach(pending -> pending.apply()); return provider; } + private static HolderLookup.RegistryLookup withLoadedTags( + MultiPackResourceManager dataResources, + HolderLookup.RegistryLookup original + ) { + @SuppressWarnings("unchecked") + net.minecraft.resources.ResourceKey> registryKey = + (net.minecraft.resources.ResourceKey>) original.key(); + Map, List>> loadedTags = TagLoader.loadTagsForRegistry( + dataResources, + registryKey, + TagLoader.ElementLookup.fromGetters(registryKey, original, original) + ); + if (loadedTags.isEmpty()) { + return original; + } + Map, HolderSet.Named> namedTags = new java.util.HashMap<>(); + loadedTags.forEach((tag, holders) -> namedTags.put(tag, createNamedTagSet(original, tag, holders))); + return new HolderLookup.RegistryLookup.Delegate<>() { + @Override + public HolderLookup.RegistryLookup parent() { + return original; + } + + @Override + public Optional> get(TagKey id) { + return Optional.ofNullable(namedTags.get(id)).or(() -> original.get(id)); + } + + @Override + public Stream> listTags() { + return Stream.concat(namedTags.values().stream(), original.listTags().filter(tag -> !namedTags.containsKey(tag.key()))); + } + }; + } + + @SuppressWarnings("unchecked") + private static HolderSet.Named createNamedTagSet( + HolderLookup.RegistryLookup owner, + TagKey key, + List> contents + ) { + HolderSet.Named named = HolderSetNamedAccessor.fer$create(owner, key); + ((HolderSetNamedAccessor) named).fer$bind(contents); + return named; + } + private static void bootstrap() { // Trigger static initialization exactly once to bind GUI-safe item components. PROVIDER.listRegistryKeys(); diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/accessor/HolderSetNamedAccessor.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/accessor/HolderSetNamedAccessor.java new file mode 100644 index 0000000..0bae23e --- /dev/null +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/mixin/accessor/HolderSetNamedAccessor.java @@ -0,0 +1,21 @@ +package it.crystalnest.fancy_entity_renderer.mixin.accessor; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderOwner; +import net.minecraft.core.HolderSet; +import net.minecraft.tags.TagKey; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.List; + +@Mixin(HolderSet.Named.class) +public interface HolderSetNamedAccessor { + @Invoker("") + static HolderSet.Named fer$create(HolderOwner owner, TagKey key) { + throw new AssertionError(); + } + + @Invoker("bind") + void fer$bind(List> contents); +} diff --git a/common/src/main/resources/fancy_entity_renderer.mixins.json b/common/src/main/resources/fancy_entity_renderer.mixins.json index 518e858..8d8bbbd 100644 --- a/common/src/main/resources/fancy_entity_renderer.mixins.json +++ b/common/src/main/resources/fancy_entity_renderer.mixins.json @@ -3,7 +3,9 @@ "minVersion": "0.8", "package": "${group}.${mod_id}.mixin", "compatibilityLevel": "JAVA_${java_version}", - "mixins": [], + "mixins": [ + "accessor.HolderSetNamedAccessor" + ], "client": [ "client.EntityRenderDispatcherMixin", "client.GuiEntityRendererMixin", From a3d32c2c9fc66e1149b5c0a85873021f5e5c00bb Mon Sep 17 00:00:00 2001 From: Keksuccino Date: Mon, 6 Apr 2026 03:13:25 +0200 Subject: [PATCH 15/15] Port to MC 26.1.1 --- .../fancy_entity_renderer/client/screen/FerDevTestScreen.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java b/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java index 28496bb..47d7f4d 100644 --- a/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java +++ b/common/src/main/java/it/crystalnest/fancy_entity_renderer/client/screen/FerDevTestScreen.java @@ -95,6 +95,7 @@ protected void init() { .setHeadWearable(Items.CHAINMAIL_HELMET) .setChestWearable(Items.ELYTRA) .setFeetWearable(Items.CHAINMAIL_BOOTS) + .setRightParrot(Parrot.Variant.GRAY) ); rightY += rogueHeight + cardSpacing; @@ -170,4 +171,4 @@ private void addCard(int x, int labelY, int width, int height, Component label, private record DemoCard(int x, int labelY, int y, int width, int height) {} -} \ No newline at end of file +}