From ce46c0feb25b6127a1ce4e5624a05acf5ce16a71 Mon Sep 17 00:00:00 2001 From: Anthony Hilyard Date: Thu, 22 Dec 2022 15:20:04 -0800 Subject: Added new selectors and item rendering capability. - Added wildcard item selector. - Added selector negation. - Added selector combination. - Item detail models can now be rendered via the custom item renderer. - Fixed several tooltip rendering bugs. - Added Minecraft 1.19.3 support. --- .../com/anthonyhilyard/iceberg/IcebergClient.java | 26 +- .../java/com/anthonyhilyard/iceberg/Loader.java | 3 + .../anthonyhilyard/iceberg/mixin/ScreenMixin.java | 39 +- .../iceberg/mixin/TooltipRenderUtilMixin.java | 120 ++++ .../iceberg/renderer/CheckedBufferSource.java | 68 +++ .../iceberg/renderer/CustomItemRenderer.java | 617 ++++++++++++++++++++- .../iceberg/renderer/VertexCollector.java | 79 +++ .../iceberg/util/DynamicResourcePack.java | 59 +- .../com/anthonyhilyard/iceberg/util/GuiHelper.java | 2 +- .../com/anthonyhilyard/iceberg/util/Selectors.java | 85 ++- .../com/anthonyhilyard/iceberg/util/Tooltips.java | 229 ++++++-- src/main/resources/META-INF/accesstransformer.cfg | 8 +- src/main/resources/iceberg.mixins.json | 3 +- 13 files changed, 1205 insertions(+), 133 deletions(-) create mode 100644 src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java create mode 100644 src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java create mode 100644 src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java (limited to 'src') diff --git a/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java b/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java index edbfc1b..a5bd0dd 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java +++ b/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java @@ -1,6 +1,14 @@ package com.anthonyhilyard.iceberg; +import com.anthonyhilyard.iceberg.util.Tooltips.TitleBreakComponent; +import com.mojang.datafixers.util.Either; + +import net.minecraft.network.chat.FormattedText; +import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RenderTooltipEvent.GatherComponents; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; @@ -14,7 +22,23 @@ public class IcebergClient public void onClientSetup(FMLClientSetupEvent event) { - + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onGatherComponentsEventEnd(GatherComponents event) + { + if (event.getTooltipElements().size() > 1) + { + // Insert a title break component after the first formattedText component. + for (int i = 0; i < event.getTooltipElements().size(); i++) + { + if (event.getTooltipElements().get(i).left().isPresent()) + { + event.getTooltipElements().add(i + 1, Either.right(new TitleBreakComponent())); + break; + } + } + } } // @SubscribeEvent diff --git a/src/main/java/com/anthonyhilyard/iceberg/Loader.java b/src/main/java/com/anthonyhilyard/iceberg/Loader.java index ae7db63..8d6ceb4 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/Loader.java +++ b/src/main/java/com/anthonyhilyard/iceberg/Loader.java @@ -3,6 +3,8 @@ package com.anthonyhilyard.iceberg; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import com.anthonyhilyard.iceberg.util.Tooltips.TitleBreakComponent; + import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -23,6 +25,7 @@ public class Loader { if (FMLEnvironment.dist == Dist.CLIENT) { + TitleBreakComponent.registerFactory(); IcebergClient mod = new IcebergClient(); MinecraftForge.EVENT_BUS.register(IcebergClient.class); FMLJavaModLoadingContext.get().getModEventBus().addListener(mod::onClientSetup); diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java index 4c7e0c8..68f2e88 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java +++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java @@ -3,6 +3,7 @@ package com.anthonyhilyard.iceberg.mixin; import java.util.List; import com.anthonyhilyard.iceberg.events.RenderTooltipExtEvent; +import com.anthonyhilyard.iceberg.util.Tooltips; import com.google.common.collect.Lists; import com.mojang.blaze3d.vertex.PoseStack; @@ -18,9 +19,12 @@ import net.minecraft.client.gui.Font; import net.minecraft.client.gui.components.events.AbstractContainerEventHandler; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; +import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil; +import net.minecraft.network.chat.TextColor; import net.minecraft.world.item.ItemStack; -import net.minecraftforge.client.ForgeHooksClient; import net.minecraftforge.client.event.RenderTooltipEvent; import net.minecraftforge.common.MinecraftForge; @@ -39,17 +43,46 @@ public class ScreenMixin extends AbstractContainerEventHandler @Shadow private final List children = Lists.newArrayList(); + @Inject(method = "renderTooltipInternal", at = @At(value = "HEAD")) + private void icebergRenderTooltipInternalHead(PoseStack poseStack, List components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info) + { + // If the tooltip stack is empty, try to get the stack from the slot under the mouse. + // This is needed for the creative inventory screen, which doesn't set the tooltip stack. + Screen self = (Screen) (Object) this; + if (tooltipStack.isEmpty() && self instanceof AbstractContainerScreen containerScreen && containerScreen.getSlotUnderMouse() != null) + { + tooltipStack = containerScreen.getSlotUnderMouse().getItem(); + } + } + + @Inject(method = "renderTooltipInternal", at = @At(value = "TAIL")) + private void icebergRenderTooltipInternalTail(PoseStack poseStack, List components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info) + { + tooltipStack = ItemStack.EMPTY; + } + @Inject(method = "renderTooltipInternal", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/entity/ItemRenderer;blitOffset:F", ordinal = 2, shift = Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void renderTooltipInternal(PoseStack poseStack, List components, int x, int y, CallbackInfo info, RenderTooltipEvent.Pre pre, int tooltipWidth, int tooltipHeight, int postX, int postY) + private void icebergRenderTooltipInternalPost(PoseStack poseStack, List components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info, RenderTooltipEvent.Pre preEvent, int tooltipWidth, int tooltipHeight, int postX, int postY) { if (!components.isEmpty()) { - MinecraftForge.EVENT_BUS.post(new RenderTooltipExtEvent.Post(tooltipStack, poseStack, postX, postY, ForgeHooksClient.getTooltipFont(tooltipFont, tooltipStack, font), tooltipWidth, tooltipHeight, components, false)); + MinecraftForge.EVENT_BUS.post(new RenderTooltipExtEvent.Post(preEvent.getItemStack(), poseStack, postX, postY, preEvent.getFont(), tooltipWidth, tooltipHeight, components, false)); } } + @Inject(method = "renderTooltipInternal", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderTooltipBackground(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", + ordinal = 0, shift = Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + private void renderTooltipInternalColor(PoseStack poseStack, List components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info, RenderTooltipEvent.Pre preEvent) + { + RenderTooltipExtEvent.Color colorEvent = new RenderTooltipExtEvent.Color(preEvent.getItemStack(), poseStack, x, y, preEvent.getFont(), TooltipRenderUtil.BACKGROUND_COLOR, TooltipRenderUtil.BACKGROUND_COLOR, TooltipRenderUtil.BORDER_COLOR_TOP, TooltipRenderUtil.BORDER_COLOR_BOTTOM, components, false, 0); + MinecraftForge.EVENT_BUS.post(colorEvent); + + Tooltips.currentColors = new Tooltips.TooltipColors(TextColor.fromRgb(colorEvent.getBackgroundStart()), TextColor.fromRgb(colorEvent.getBackgroundEnd()), TextColor.fromRgb(colorEvent.getBorderStart()), TextColor.fromRgb(colorEvent.getBorderEnd())); + } + @Override public List children() { diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java new file mode 100644 index 0000000..791ece3 --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java @@ -0,0 +1,120 @@ +package com.anthonyhilyard.iceberg.mixin; + +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.anthonyhilyard.iceberg.util.Tooltips; +import com.mojang.blaze3d.vertex.BufferBuilder; + +import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil; +import net.minecraft.network.chat.TextColor; + +@Mixin(TooltipRenderUtil.class) +public class TooltipRenderUtilMixin +{ + @Unique + private static TextColor horizontalLineColor; + + @Shadow + @Final + private static int BACKGROUND_COLOR; + + @Shadow + @Final + private static int BORDER_COLOR_TOP; + + @Shadow + @Final + private static int BORDER_COLOR_BOTTOM; + + @Inject(method = "renderTooltipBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 0)) + private static void icebergRenderTooltipBackgroundOne(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, CallbackInfo info) + { + horizontalLineColor = Tooltips.currentColors.backgroundColorStart(); + } + + @Inject(method = "renderTooltipBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 1)) + private static void icebergRenderTooltipBackgroundTwo(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, CallbackInfo info) + { + horizontalLineColor = Tooltips.currentColors.backgroundColorEnd(); + } + + @Inject(method = "renderFrameGradient", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 0)) + private static void icebergRenderFrameGradientOne(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, int color1, int color2, CallbackInfo info) + { + horizontalLineColor = Tooltips.currentColors.borderColorStart(); + } + + @Inject(method = "renderFrameGradient", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 1)) + private static void icebergRenderFrameGradientTwo(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, int color1, int color2, CallbackInfo info) + { + horizontalLineColor = Tooltips.currentColors.borderColorEnd(); + } + + @Inject(method = "renderHorizontalLine", at = @At(value = "HEAD"), cancellable = true) + private static void icebergRenderHorizontalLine(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int z, int color, CallbackInfo info) + { + if (color != BACKGROUND_COLOR && color != BORDER_COLOR_TOP && color != BORDER_COLOR_BOTTOM) + { + // Do default behavior so other mods that change colors can still work. + } + else + { + // Replace the rendered colors with the ones previously stored. + int renderColor = horizontalLineColor.getValue(); + painter.blit(matrix, bufferbuilder, x, y, x + width, y + 1, z, renderColor, renderColor); + info.cancel(); + } + } + + @Inject(method = "renderRectangle", at = @At(value = "HEAD"), cancellable = true) + private static void icebergRenderRectangle(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, int color, CallbackInfo info) + { + if (color != BACKGROUND_COLOR) + { + // Do default behavior so other mods that change colors can still work. + } + else + { + // Replace the rendered colors with the ones previously stored. + painter.blit(matrix, bufferbuilder, x, y, x + width, y + height, z, Tooltips.currentColors.backgroundColorStart().getValue(), Tooltips.currentColors.backgroundColorEnd().getValue()); + info.cancel(); + } + } + + @Inject(method = "renderVerticalLine", at = @At(value = "HEAD"), cancellable = true) + private static void icebergRenderVerticalLine(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int height, int z, int color, CallbackInfo info) + { + if (color != BACKGROUND_COLOR) + { + // Do default behavior so other mods that change colors can still work. + } + else + { + // Replace the rendered colors with the ones previously stored. + painter.blit(matrix, bufferbuilder, x, y, x + 1, y + height, z, Tooltips.currentColors.backgroundColorStart().getValue(), Tooltips.currentColors.backgroundColorEnd().getValue()); + info.cancel(); + } + } + + @Inject(method = "renderVerticalLineGradient", at = @At(value = "HEAD"), cancellable = true) + private static void icebergRenderVerticalLineGradient(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int height, int z, int startColor, int endColor, CallbackInfo info) + { + if (startColor != BORDER_COLOR_TOP || endColor != BORDER_COLOR_BOTTOM) + { + // Do default behavior so other mods that change colors can still work. + } + else + { + // Replace the rendered colors with the ones previously stored. + painter.blit(matrix, bufferbuilder, x, y, x + 1, y + height, z, Tooltips.currentColors.borderColorStart().getValue(), Tooltips.currentColors.borderColorEnd().getValue()); + info.cancel(); + } + } +} diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java new file mode 100644 index 0000000..5d34821 --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java @@ -0,0 +1,68 @@ +package com.anthonyhilyard.iceberg.renderer; + +import com.mojang.blaze3d.vertex.VertexConsumer; + +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; + +public final class CheckedBufferSource implements MultiBufferSource +{ + private boolean hasRendered = false; + private final MultiBufferSource bufferSource; + + public CheckedBufferSource(MultiBufferSource bufferSource) + { + this.bufferSource = bufferSource; + } + + @Override + public VertexConsumer getBuffer(RenderType renderType) + { + final VertexConsumer vertexConsumer = bufferSource.getBuffer(renderType); + VertexConsumer vertexConsumerWrap = new VertexConsumer() + { + @Override + public VertexConsumer vertex(double x, double y, double z) + { + hasRendered = true; + return vertexConsumer.vertex(x, y, z); + } + + @Override + public VertexConsumer color(int r, int g, int b, int a) { return vertexConsumer.color(r, g, b, a); } + + @Override + public VertexConsumer uv(float u, float v) { return vertexConsumer.uv(u, v); } + + @Override + public VertexConsumer overlayCoords(int x, int y) { return vertexConsumer.overlayCoords(x, y); } + + @Override + public VertexConsumer uv2(int u, int v) { return vertexConsumer.uv2(u, v); } + + @Override + public VertexConsumer normal(float x, float y, float z) { return vertexConsumer.normal(x, y, z); } + + @Override + public void endVertex() { vertexConsumer.endVertex(); } + + @Override + public void defaultColor(int r, int g, int b, int a) { vertexConsumer.defaultColor(r, g, b, a); } + + @Override + public void unsetDefaultColor() { vertexConsumer.unsetDefaultColor(); } + }; + + return vertexConsumerWrap; + } + + public boolean hasRendered() + { + return hasRendered; + } + + public void reset() + { + hasRendered = false; + } +} diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java index b77bb09..301b4c4 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java +++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java @@ -1,50 +1,115 @@ package com.anthonyhilyard.iceberg.renderer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import com.google.common.collect.Maps; + +import org.joml.Matrix4f; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import org.lwjgl.opengl.GL11; + import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.datafixers.util.Pair; +import com.mojang.math.MatrixUtil; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.pipeline.MainTarget; import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.platform.Lighting; +import com.mojang.blaze3d.platform.GlStateManager.SourceFactor; +import com.mojang.blaze3d.platform.GlStateManager.DestFactor; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.lwjgl.opengl.GL11; - +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.entity.EntityRenderDispatcher; import net.minecraft.client.renderer.entity.ItemRenderer; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.TextureManager; - -import com.mojang.blaze3d.platform.GlStateManager.SourceFactor; -import com.mojang.blaze3d.platform.GlStateManager.DestFactor; -import com.mojang.math.Matrix4f; - import net.minecraft.client.color.item.ItemColors; import net.minecraft.client.gui.GuiComponent; import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; +import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.MultiBufferSource.BufferSource; import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.tags.ItemTags; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.animal.horse.Horse; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.vehicle.AbstractMinecart; +import net.minecraft.world.entity.vehicle.Boat; +import net.minecraft.world.entity.vehicle.ChestBoat; import net.minecraft.world.inventory.InventoryMenu; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.BoatItem; +import net.minecraft.world.item.HorseArmorItem; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.MinecartItem; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.HalfTransparentBlock; +import net.minecraft.world.level.block.StainedGlassPaneBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; + +import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.extensions.common.IClientItemExtensions; /** - * An extended ItemRenderer that allows items to be rendered to RenderTarget before drawing to screen. - * This allows alpha values to be supported properly by all item types, even semi-transparent items. + * An extended ItemRenderer with extended functionality, such as allowing items to be rendered to a RenderTarget + * before drawing to screen for alpha support, and allowing handheld item models to be rendered into the gui. */ public class CustomItemRenderer extends ItemRenderer { - @SuppressWarnings("unused") - private static final Logger LOGGER = LogManager.getLogger(); + /* Cylindrical bounds for a model. */ + private record ModelBounds(Vector3f center, float height, float radius) {} private static RenderTarget iconFrameBuffer = null; + private static ArmorStand armorStand = null; + private static AbstractMinecart minecart = null; + private static Boat boat = null; + private static Horse horse = null; + private static Pair cachedArmorStandItem = null; + private static Pair cachedHorseArmorItem = null; + private static Map modelBoundsCache = Maps.newHashMap(); + + private static final List quadDirections; + static { + quadDirections = new ArrayList<>(Arrays.asList(Direction.values())); + quadDirections.add(null); + } + private Minecraft mc; - + private final ModelManager modelManager; + private final BlockEntityWithoutLevelRenderer blockEntityRenderer; + public CustomItemRenderer(TextureManager textureManagerIn, ModelManager modelManagerIn, ItemColors itemColorsIn, BlockEntityWithoutLevelRenderer blockEntityRendererIn, Minecraft mcIn) { super(textureManagerIn, modelManagerIn, itemColorsIn, blockEntityRendererIn); mc = mcIn; + modelManager = modelManagerIn; + blockEntityRenderer = blockEntityRendererIn; // Initialize the icon framebuffer if needed. if (iconFrameBuffer == null) @@ -56,6 +121,509 @@ public class CustomItemRenderer extends ItemRenderer } } + private void renderGuiModel(ItemStack itemStack, int x, int y, Quaternionf rotation, BakedModel bakedModel) + { + mc.getTextureManager().getTexture(InventoryMenu.BLOCK_ATLAS).setFilter(false, false); + RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS); + RenderSystem.enableBlend(); + RenderSystem.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + + PoseStack modelViewStack = RenderSystem.getModelViewStack(); + modelViewStack.pushPose(); + modelViewStack.translate(x, y, 100.0f + blitOffset); + + modelViewStack.translate(8.0f, 8.0f, 0.0f); + modelViewStack.scale(1.0f, -1.0f, 1.0f); + modelViewStack.scale(16.0f, 16.0f, 16.0f); + RenderSystem.applyModelViewMatrix(); + + BufferSource bufferSource = Minecraft.getInstance().renderBuffers().bufferSource(); + boolean flatLighting = !bakedModel.usesBlockLight(); + if (flatLighting) { Lighting.setupForFlatItems(); } + + PoseStack poseStack = new PoseStack(); + renderModel(itemStack, ItemTransforms.TransformType.GUI, false, poseStack, rotation, bufferSource, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, bakedModel); + + poseStack.popPose(); + bufferSource.endBatch(); + RenderSystem.enableDepthTest(); + if (flatLighting) { Lighting.setupFor3DItems(); } + + modelViewStack.popPose(); + RenderSystem.applyModelViewMatrix(); + } + + @SuppressWarnings("deprecation") + private void renderEntityModel(Entity entity, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) + { + Minecraft minecraft = Minecraft.getInstance(); + EntityRenderDispatcher entityRenderDispatcher = minecraft.getEntityRenderDispatcher(); + Lighting.setupForEntityInInventory(); + RenderSystem.enableDepthTest(); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + entityRenderDispatcher.setRenderShadow(false); + + RenderSystem.runAsFancy(() -> entityRenderDispatcher.render(entity, 0.0, 0.0, 0.0, 0.0f, 1.0f, poseStack, bufferSource, packedLight)); + + if (bufferSource instanceof BufferSource) + { + ((BufferSource)bufferSource).endBatch(); + } + + entityRenderDispatcher.setRenderShadow(true); + + RenderSystem.applyModelViewMatrix(); + Lighting.setupFor3DItems(); + } + + private void renderModelInternal(ItemStack itemStack, ItemTransforms.TransformType transformType, boolean leftHanded, PoseStack poseStack, + Quaternionf rotation, T bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel, + Predicate bufferSourceReady) + { + Minecraft minecraft = Minecraft.getInstance(); + + if (Player.getEquipmentSlotForItem(itemStack).isArmor()) + { + if (updateArmorStand(itemStack)) + { + renderEntityModel(armorStand, poseStack, bufferSource, packedLight); + } + } + + if (!bakedModel.isCustomRenderer() && !itemStack.is(Items.TRIDENT)) + { + boolean fabulous; + if (transformType != ItemTransforms.TransformType.GUI && !transformType.firstPerson() && itemStack.getItem() instanceof BlockItem) + { + Block block = ((BlockItem)itemStack.getItem()).getBlock(); + fabulous = !(block instanceof HalfTransparentBlock) && !(block instanceof StainedGlassPaneBlock); + } + else + { + fabulous = true; + } + + if (bufferSourceReady.test(bufferSource) && itemStack.getItem() instanceof BlockItem blockItem) + { + BakedModel blockModel = null; + boolean isBlockEntity = false; + + blockModel = minecraft.getBlockRenderer().getBlockModelShaper().getBlockModel(blockItem.getBlock().defaultBlockState()); + if (blockModel != modelManager.getMissingModel()) + { + // First try rendering via the BlockEntityWithoutLevelRenderer. + blockEntityRenderer.renderByItem(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay); + } + else + { + blockModel = null; + } + + if (blockItem.getBlock().defaultBlockState().hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)) + { + // This is a double block, so we'll need to render both halves. + // First render the bottom half. + BlockState bottomState = blockItem.getBlock().defaultBlockState().setValue(BlockStateProperties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.LOWER); + BakedModel bottomModel = minecraft.getBlockRenderer().getBlockModelShaper().getBlockModel(bottomState); + renderBakedModel(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay, bottomModel, fabulous); + + // Then render the top half. + poseStack.pushPose(); + poseStack.translate(0.0f, 1.0f, 0.0f); + BlockState topState = blockItem.getBlock().defaultBlockState().setValue(BlockStateProperties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.UPPER); + BakedModel topModel = minecraft.getBlockRenderer().getBlockModelShaper().getBlockModel(topState); + renderBakedModel(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay, topModel, fabulous); + poseStack.popPose(); + } + + if (blockItem.getBlock() instanceof EntityBlock entityBlock) + { + isBlockEntity = true; + try + { + renderBlockEntity(itemStack, poseStack, bufferSource, packedLight, packedOverlay, minecraft, entityBlock, blockItem.getBlock().defaultBlockState()); + } + catch (Exception e) + { + // This can fail for things like beacons that require a level. We'll just ignore it. + } + } + + // If we still haven't rendered anything or this is a block entity, try rendering the block model. + if (blockModel != null && (bufferSourceReady.test(bufferSource) || isBlockEntity)) + { + renderBakedModel(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay, blockModel, fabulous); + } + } + + // Now try rendering entity models for items that spawn minecarts or boats. + if (bufferSourceReady.test(bufferSource) && itemStack.getItem() instanceof MinecartItem minecartItem) + { + if (updateMinecart(minecartItem)) + { + renderEntityModel(minecart, poseStack, bufferSource, packedLight); + } + } + + if (bufferSourceReady.test(bufferSource) && itemStack.getItem() instanceof BoatItem boatItem) + { + if (updateBoat(boatItem)) + { + renderEntityModel(boat, poseStack, bufferSource, packedLight); + } + } + + // If this is horse armor, render it here. + if (bufferSourceReady.test(bufferSource) && itemStack.getItem() instanceof HorseArmorItem) + { + if (updateHorse(itemStack)) + { + renderEntityModel(horse, poseStack, bufferSource, packedLight); + } + } + + // Finally, fall back to just rendering the item model. + if (bufferSourceReady.test(bufferSource)) + { + renderBakedModel(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay, bakedModel, fabulous); + } + } + else if (bufferSourceReady.test(bufferSource)) + { + IClientItemExtensions.of(itemStack).getCustomRenderer().renderByItem(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay); + } + } + + private void renderModel(ItemStack itemStack, ItemTransforms.TransformType transformType, boolean leftHanded, PoseStack poseStack, Quaternionf rotation, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel) + { + if (!itemStack.isEmpty()) + { + boolean isBlockItem = false, spawnsEntity = false, isArmor = false; + if (itemStack.getItem() instanceof BlockItem blockItem) + { + isBlockItem = true; + } + else if (itemStack.getItem() instanceof MinecartItem || itemStack.getItem() instanceof BoatItem) + { + spawnsEntity = true; + } + + if (Player.getEquipmentSlotForItem(itemStack).isArmor()) + { + isArmor = true; + } + + poseStack.pushPose(); + + if (isBlockItem || spawnsEntity) + { + // Apply the standard block rotation so block entities match other blocks. + poseStack.mulPose(new Quaternionf().rotationXYZ((float)Math.toRadians(30.0f), (float)Math.toRadians(225.0f), 0.0f)); + } + else + { + ForgeHooksClient.handleCameraTransforms(poseStack, bakedModel, transformType, leftHanded); + } + + // Get the model bounds. + ModelBounds modelBounds = getModelBounds(itemStack, transformType, leftHanded, poseStack, rotation, bufferSource, packedLight, packedOverlay, bakedModel); + + // Undo the camera transforms now that we have the model bounds. + poseStack.popPose(); + poseStack.pushPose(); + + // Rotate the model. + poseStack.mulPose(rotation); + + // Scale the model to fit. + float scale = 1.0f / Math.max(modelBounds.height, modelBounds.radius * 2.0f); + scale *= isBlockItem ? 0.8f : 1.0f; + + // Adjust the scale based on the armor type. + if (isArmor) + { + switch (Player.getEquipmentSlotForItem(itemStack)) + { + case HEAD: + scale *= 0.75f; + break; + case LEGS: + scale *= 1.1f; + break; + default: + break; + } + } + poseStack.scale(scale, scale, scale); + + // Translate the model to the center of the item. + poseStack.translate(-modelBounds.center.x(), -modelBounds.center.y(), -modelBounds.center.z()); + + // Reapply the camera transforms. + if (isBlockItem || spawnsEntity) + { + // Apply the standard block rotation so block entities match other blocks. + poseStack.mulPose(new Quaternionf().rotationXYZ((float)Math.toRadians(30.0f), (float)Math.toRadians(225.0f), 0.0f)); + } + else + { + bakedModel = ForgeHooksClient.handleCameraTransforms(poseStack, bakedModel, transformType, leftHanded); + } + + CheckedBufferSource checkedBufferSource = new CheckedBufferSource(bufferSource); + renderModelInternal(itemStack, transformType, leftHanded, poseStack, rotation, checkedBufferSource, packedLight, packedOverlay, bakedModel, b -> !b.hasRendered()); + + poseStack.popPose(); + } + } + + private void renderBlockEntity(ItemStack itemStack, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, + int packedOverlay, Minecraft minecraft, EntityBlock entityBlock, BlockState blockState) throws Exception + { + // If we didn't render via the BlockEntityWithoutLevelRenderer, now check if this is a block entity and render that. + BlockEntity blockEntity = entityBlock.newBlockEntity(BlockPos.ZERO, blockState); + if (blockEntity != null) + { + if (itemStack.getTag() != null) + { + blockEntity.load(itemStack.getTag()); + } + + BlockEntityRenderer renderer = minecraft.getBlockEntityRenderDispatcher().getRenderer(blockEntity); + if (renderer != null) + { + renderer.render(blockEntity, minecraft.getFrameTime(), poseStack, bufferSource, packedLight, packedOverlay); + } + } + } + + private void renderBakedModel(ItemStack itemStack, ItemTransforms.TransformType transformType, PoseStack poseStack, + MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel, boolean fabulous) + { + for (var model : bakedModel.getRenderPasses(itemStack, fabulous)) + { + for (var rendertype : model.getRenderTypes(itemStack, fabulous)) + { + VertexConsumer vertexconsumer; + if (itemStack.is(ItemTags.COMPASSES) && itemStack.hasFoil()) + { + poseStack.pushPose(); + PoseStack.Pose posestack$pose = poseStack.last(); + if (transformType == ItemTransforms.TransformType.GUI) + { + MatrixUtil.mulComponentWise(posestack$pose.pose(), 0.5F); + } + else if (transformType.firstPerson()) + { + MatrixUtil.mulComponentWise(posestack$pose.pose(), 0.75F); + } + + if (fabulous) + { + vertexconsumer = getCompassFoilBufferDirect(bufferSource, rendertype, posestack$pose); + } + else + { + vertexconsumer = getCompassFoilBuffer(bufferSource, rendertype, posestack$pose); + } + + poseStack.popPose(); + } + else if (fabulous) + { + vertexconsumer = getFoilBufferDirect(bufferSource, rendertype, true, itemStack.hasFoil()); + } + else + { + vertexconsumer = getFoilBuffer(bufferSource, rendertype, true, itemStack.hasFoil()); + } + + renderModelLists(model, itemStack, packedLight, packedOverlay, poseStack, vertexconsumer); + } + } + } + + private boolean updateArmorStand(ItemStack itemStack) + { + EquipmentSlot equipmentSlot = Player.getEquipmentSlotForItem(itemStack); + if (!equipmentSlot.isArmor()) + { + // This isn't armor, so don't render anything. + return false; + } + + if (armorStand == null) + { + Minecraft minecraft = Minecraft.getInstance(); + armorStand = EntityType.ARMOR_STAND.create(minecraft.level); + armorStand.setInvisible(true); + } + + // If somehow the armor stand is still null, then we can't render anything. + if (armorStand == null) + { + return false; + } + + // If the item has changed, then we need to update the armor stand. + if (cachedArmorStandItem != Pair.of(itemStack.getItem(), itemStack.getTag())) + { + // Clear the armor stand. + for (EquipmentSlot slot : EquipmentSlot.values()) + { + armorStand.setItemSlot(slot, ItemStack.EMPTY); + } + + // Equip the armor stand with the armor. + armorStand.setItemSlot(equipmentSlot, itemStack); + + cachedArmorStandItem = Pair.of(itemStack.getItem(), itemStack.getTag()); + } + return true; + } + + private boolean updateMinecart(MinecartItem item) + { + if (minecart == null || minecart.getMinecartType() != item.type) + { + Minecraft minecraft = Minecraft.getInstance(); + minecart = AbstractMinecart.createMinecart(minecraft.level, 0, 0, 0, item.type); + } + + // If somehow the minecart is still null, then we can't render anything. + return minecart != null; + } + + private boolean updateBoat(BoatItem item) + { + if (boat == null || boat.getVariant() != item.type || (boat instanceof ChestBoat) != item.hasChest) + { + Minecraft minecraft = Minecraft.getInstance(); + boat = item.hasChest ? new ChestBoat(minecraft.level, 0, 0, 0) : new Boat(minecraft.level, 0, 0, 0); + boat.setVariant(item.type); + } + + // If somehow the boat is still null, then we can't render anything. + return boat != null; + } + + private boolean updateHorse(ItemStack horseArmorItem) + { + // If this isn't a horse armor item, we can't render anything. + if (!(horseArmorItem.getItem() instanceof HorseArmorItem)) + { + return false; + } + + if (horse == null) + { + Minecraft minecraft = Minecraft.getInstance(); + horse = EntityType.HORSE.create(minecraft.level); + horse.setInvisible(true); + horse.canUpdate(false); + } + + // If somehow the horse is still null, then we can't render anything. + if (horse == null) + { + return false; + } + + // If the item has changed, then we need to update the horse. + if (cachedHorseArmorItem != Pair.of(horseArmorItem.getItem(), horseArmorItem.getTag())) + { + // Equip the horse with the armor. + horse.setItemSlot(EquipmentSlot.CHEST, horseArmorItem); + + cachedHorseArmorItem = Pair.of(horseArmorItem.getItem(), horseArmorItem.getTag()); + } + return true; + } + + private ModelBounds boundsFromVertices(List vertices) + { + Vector3f center = new Vector3f(); + float radius = 0.0F; + float height = 0.0F; + + float minX = Float.MAX_VALUE; + float minY = Float.MAX_VALUE; + float minZ = Float.MAX_VALUE; + float maxX = Float.MIN_VALUE; + float maxY = Float.MIN_VALUE; + float maxZ = Float.MIN_VALUE; + + for (Vector3f vertex : vertices) + { + minX = Math.min(minX, vertex.x); + minY = Math.min(minY, vertex.y); + minZ = Math.min(minZ, vertex.z); + maxX = Math.max(maxX, vertex.x); + maxY = Math.max(maxY, vertex.y); + maxZ = Math.max(maxZ, vertex.z); + } + + center = new Vector3f((minX + maxX) / 2.0F, (minY + maxY) / 2.0F, (minZ + maxZ) / 2.0F); + height = maxY - minY; + + for (Vector3f vertex : vertices) + { + radius = Math.max(radius, (float) Math.sqrt((vertex.x - center.x) * (vertex.x - center.x) + (vertex.y - center.y) * (vertex.y - center.y))); + } + + return new ModelBounds(center, height, radius); + } + + private ModelBounds getModelBounds(ItemStack itemStack, ItemTransforms.TransformType transformType, boolean leftHanded, PoseStack poseStack, + Quaternionf rotation, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel) + { + if (!modelBoundsCache.containsKey(itemStack.getItem())) + { + VertexCollector vertexCollector = new VertexCollector(); + renderModelInternal(itemStack, transformType, leftHanded, poseStack, rotation, vertexCollector, packedLight, packedOverlay, bakedModel, b -> b.getVertices().isEmpty()); + + // Now store the bounds in the cache. + modelBoundsCache.put(itemStack.getItem(), boundsFromVertices(vertexCollector.getVertices())); + } + + return modelBoundsCache.get(itemStack.getItem()); + } + + public void renderDetailModelIntoGUI(ItemStack stack, int x, int y, Quaternionf rotation) + { + Minecraft minecraft = Minecraft.getInstance(); + BakedModel bakedModel = minecraft.getItemRenderer().getModel(stack, minecraft.level, minecraft.player, 0); + + blitOffset += 50.0f; + + try + { + renderGuiModel(stack, x, y, rotation, bakedModel); + } + catch (Throwable throwable) + { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Rendering item"); + CrashReportCategory crashreportcategory = crashreport.addCategory("Item being rendered"); + crashreportcategory.setDetail("Item Type", () -> { + return String.valueOf((Object)stack.getItem()); + }); + crashreportcategory.setDetail("Registry Name", () -> String.valueOf(net.minecraftforge.registries.ForgeRegistries.ITEMS.getKey(stack.getItem()))); + crashreportcategory.setDetail("Item Damage", () -> { + return String.valueOf(stack.getDamageValue()); + }); + crashreportcategory.setDetail("Item NBT", () -> { + return String.valueOf((Object)stack.getTag()); + }); + crashreportcategory.setDetail("Item Foil", () -> { + return String.valueOf(stack.hasFoil()); + }); + throw new ReportedException(crashreport); + } + + blitOffset -= 50.0f; + } + public void renderItemModelIntoGUIWithAlpha(ItemStack stack, int x, int y, float alpha) { BakedModel bakedModel = mc.getItemRenderer().getModel(stack, null, null, 0); @@ -65,9 +633,12 @@ public class CustomItemRenderer extends ItemRenderer iconFrameBuffer.clear(Minecraft.ON_OSX); iconFrameBuffer.bindWrite(true); + Matrix4f matrix = new Matrix4f(); + matrix.setOrtho(0.0f, iconFrameBuffer.width, iconFrameBuffer.height, 0.0f, 1000.0f, 3000.0f); + RenderSystem.clear(GL11.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX); RenderSystem.backupProjectionMatrix(); - RenderSystem.setProjectionMatrix(Matrix4f.orthographic(0.0f, iconFrameBuffer.width, iconFrameBuffer.height, 0.0f, 1000.0f, 3000.0f)); + RenderSystem.setProjectionMatrix(matrix); Lighting.setupFor3DItems(); @@ -75,23 +646,23 @@ public class CustomItemRenderer extends ItemRenderer RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS); RenderSystem.enableBlend(); RenderSystem.blendFuncSeparate(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ONE_MINUS_SRC_ALPHA); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); PoseStack modelViewStack = RenderSystem.getModelViewStack(); modelViewStack.pushPose(); modelViewStack.setIdentity(); modelViewStack.translate(48.0f, 48.0f, -2000.0f); - modelViewStack.scale(96.0F, 96.0F, 96.0F); + modelViewStack.scale(96.0f, 96.0f, 96.0f); RenderSystem.applyModelViewMatrix(); PoseStack poseStack = new PoseStack(); - MultiBufferSource.BufferSource multibuffersource$buffersource = Minecraft.getInstance().renderBuffers().bufferSource(); - boolean flag = !bakedModel.usesBlockLight(); - if (flag) { Lighting.setupForFlatItems(); } + BufferSource bufferSource = Minecraft.getInstance().renderBuffers().bufferSource(); + boolean flatLighting = !bakedModel.usesBlockLight(); + if (flatLighting) { Lighting.setupForFlatItems(); } - render(stack, ItemTransforms.TransformType.GUI, false, poseStack, multibuffersource$buffersource, 0xF000F0, OverlayTexture.NO_OVERLAY, bakedModel); - multibuffersource$buffersource.endBatch(); + render(stack, ItemTransforms.TransformType.GUI, false, poseStack, bufferSource, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, bakedModel); + bufferSource.endBatch(); RenderSystem.enableDepthTest(); - if (flag) { Lighting.setupFor3DItems(); } + if (flatLighting) { Lighting.setupFor3DItems(); } modelViewStack.popPose(); RenderSystem.applyModelViewMatrix(); diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java new file mode 100644 index 0000000..7554893 --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java @@ -0,0 +1,79 @@ +package com.anthonyhilyard.iceberg.renderer; + +import java.util.List; + +import org.apache.commons.compress.utils.Lists; +import org.joml.Vector3f; + +import com.mojang.blaze3d.vertex.VertexConsumer; + +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; + +public class VertexCollector implements MultiBufferSource +{ + private final List vertices = Lists.newArrayList(); + private final Vector3f currentVertex = new Vector3f(); + private int currentAlpha = 255; + private int defaultAlpha = 255; + + @Override + public VertexConsumer getBuffer(RenderType renderType) + { + return new VertexConsumer() + { + @Override + public VertexConsumer vertex(double x, double y, double z) + { + currentVertex.set((float) x, (float) y, (float) z); + currentAlpha = defaultAlpha; + return this; + } + + @Override + public VertexConsumer color(int r, int g, int b, int a) + { + currentAlpha = a; + return this; + } + + @Override + public VertexConsumer uv(float u, float v) { return this; } + + @Override + public VertexConsumer overlayCoords(int x, int y) { return this; } + + @Override + public VertexConsumer uv2(int u, int v) { return this; } + + @Override + public VertexConsumer normal(float x, float y, float z) { return this; } + + @Override + public void endVertex() + { + if (currentAlpha >= 25) + { + vertices.add(new Vector3f(currentVertex)); + } + } + + @Override + public void defaultColor(int r, int g, int b, int a) + { + defaultAlpha = a; + } + + @Override + public void unsetDefaultColor() + { + defaultAlpha = 255; + } + }; + } + + public List getVertices() + { + return vertices; + } +} diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/DynamicResourcePack.java b/src/main/java/com/anthonyhilyard/iceberg/util/DynamicResourcePack.java index a59449a..4c0bfbf 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/DynamicResourcePack.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/DynamicResourcePack.java @@ -3,19 +3,18 @@ package com.anthonyhilyard.iceberg.util; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Collectors; + +import javax.annotation.Nullable; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackResources; import net.minecraft.server.packs.PackType; import net.minecraft.server.packs.metadata.MetadataSectionSerializer; +import net.minecraft.server.packs.resources.IoSupplier; /** * DynamicResourcePack allows resources that are defined arbitrarily to do cool things with resources. @@ -26,7 +25,7 @@ public class DynamicResourcePack implements PackResources private record DynamicResourceKey(String type, String namespace, String path) {} private final String packName; - private Map> dynamicResourceMap = new HashMap>(); + private Map> dynamicResourceMap = new HashMap>(); public DynamicResourcePack(String packName) { @@ -52,17 +51,17 @@ public class DynamicResourcePack implements PackResources } } - public boolean registerResource(PackType type, ResourceLocation location, Supplier resourceSupplier) + public boolean registerResource(PackType type, ResourceLocation location, IoSupplier resourceSupplier) { return register(type.getDirectory(), location.getNamespace(), location.getPath(), resourceSupplier); } - public boolean registerRootResource(String path, Supplier resourceSupplier) + public boolean registerRootResource(String path, IoSupplier resourceSupplier) { return register("root", "", path, resourceSupplier); } - private boolean register(String directory, String namespace, String path, Supplier resourceSupplier) + private boolean register(String directory, String namespace, String path, IoSupplier resourceSupplier) { DynamicResourceKey key = new DynamicResourceKey(directory, namespace, path); if (!dynamicResourceMap.containsKey(key)) @@ -74,23 +73,39 @@ public class DynamicResourcePack implements PackResources } @Override - public InputStream getRootResource(String path) throws IOException + @Nullable + public IoSupplier getRootResource(String... path) { - return getResource("root", "", path); + try + { + return getResource("root", "", String.join("/", path)); + } + catch (IOException e) + { + return null; + } } @Override - public InputStream getResource(PackType type, ResourceLocation location) throws IOException + @Nullable + public IoSupplier getResource(PackType type, ResourceLocation location) { - return getResource(type.getDirectory(), location.getNamespace(), location.getPath()); + try + { + return getResource(type.getDirectory(), location.getNamespace(), location.getPath()); + } + catch (IOException e) + { + return null; + } } - private InputStream getResource(String directory, String namespace, String path) throws IOException + private IoSupplier getResource(String directory, String namespace, String path) throws IOException { DynamicResourceKey key = new DynamicResourceKey(directory, namespace, path); if (dynamicResourceMap.containsKey(key)) { - return dynamicResourceMap.get(key).get(); + return dynamicResourceMap.get(key); } else { @@ -99,21 +114,13 @@ public class DynamicResourcePack implements PackResources } @Override - public Collection getResources(PackType type, String namespace, String path, Predicate filter) + public void listResources(PackType type, String namespace, String path, ResourceOutput output) { - return dynamicResourceMap.entrySet().stream() + dynamicResourceMap.entrySet().stream() .filter(entry -> entry.getKey().namespace.contentEquals(namespace)) .filter(entry -> entry.getKey().path.startsWith(path)) .filter(entry -> entry.getKey().type.contentEquals(type.getDirectory())) - .filter(entry -> filter.test(new ResourceLocation(entry.getKey().namespace, entry.getKey().path))) - .map(entry -> new ResourceLocation(namespace, entry.getKey().path)) - .collect(Collectors.toList()); - } - - @Override - public boolean hasResource(PackType type, ResourceLocation location) - { - return dynamicResourceMap.containsKey(new DynamicResourceKey(type.getDirectory(), location.getNamespace(), location.getPath())); + .forEach(entry -> output.accept(new ResourceLocation(namespace, entry.getKey().path), entry.getValue())); } @Override @@ -139,7 +146,7 @@ public class DynamicResourcePack implements PackResources } @Override - public String getName() + public String packId() { return packName; } diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java b/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java index 73800cb..1421c37 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java @@ -7,7 +7,7 @@ import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.BufferUploader; import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.VertexFormat; -import com.mojang.math.Matrix4f; +import org.joml.Matrix4f; public class GuiHelper { diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java b/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java index b21c168..df9e292 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java @@ -9,7 +9,7 @@ import java.util.function.BiPredicate; import java.util.List; import net.minecraft.client.Minecraft; -import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NumericTag; import net.minecraft.nbt.Tag; @@ -87,14 +87,17 @@ public class Selectors public static List selectorDocumentation() { return Arrays.asList( - new SelectorDocumentation("Item name", "Use item name for vanilla items or include mod name for modded items.", "minecraft:stick", "iron_ore"), - new SelectorDocumentation("Tag", "$ followed by tag name.", "$forge:stone", "$planks"), - new SelectorDocumentation("Mod name", "@ followed by mod identifier.", "@spoiledeggs"), - new SelectorDocumentation("Rarity", "! followed by item's rarity. This is ONLY vanilla rarities.", "!uncommon", "!rare", "!epic"), - new SelectorDocumentation("Item name color", "# followed by color hex code, the hex code must match exactly.", "#23F632"), - new SelectorDocumentation("Display name", "% followed by any text. Will match any item with this text in its tooltip display name.", "%Netherite", "%[Uncommon]"), - new SelectorDocumentation("Tooltip text", "Will match any item with this text anywhere in the tooltip text (besides the name).", "^Legendary"), - new SelectorDocumentation("NBT tag", "& followed by tag name and optional comparator (=, >, <, or !=) and value, in the format or just .", "&Damage=0", "&Tier>1", "&map!=128", "&Enchantments") + new SelectorDocumentation("Match all", "Specifying just an asterisk (*) will match all items.", "*"), + new SelectorDocumentation("Item ID", "Use item ID to match single items. Must include mod name for modded items.", "minecraft:stick", "iron_ore", "spoiledeggs:spoiled_egg"), + new SelectorDocumentation("Tag", "$ followed by tag name to match all items with that tag.", "$forge:stone", "$planks"), + new SelectorDocumentation("Mod name", "@ followed by mod identifier to match all items from that mod.", "@spoiledeggs"), + new SelectorDocumentation("Rarity", "! followed by item's rarity to match all items with that rarity. This is ONLY vanilla rarities.", "!uncommon", "!rare", "!epic"), + new SelectorDocumentation("Item name color", "# followed by color hex code, to match all items with that exact color item name.", "#23F632"), + new SelectorDocumentation("Display name", "% followed by any text. Will match any item with this text (case-sensitive) in its tooltip display name.", "%Netherite", "%Uncommon"), + new SelectorDocumentation("Tooltip text", "^ followed by any text. Will match any item with this text (case-sensitive) anywhere in the tooltip text (besides the name).", "^Legendary"), + new SelectorDocumentation("NBT tag", "& followed by tag name and optional comparator (=, >, <, or !=) and value, in the format or just .", "&Damage=0", "&Tier>1", "&map!=128", "&Enchantments"), + new SelectorDocumentation("Negation", "~ followed by any selector above. This selector will be negated, matching every item that does NOT match the selector.", "~minecraft:stick", "~!uncommon", "~@minecraft"), + new SelectorDocumentation("Combining selectors", "Any number of selectors can be combined by separating them with a plus sign.", "minecraft:diamond_sword+&Enchantments", "minecraft:stick+~!common+&Damage=0") ); } @@ -105,6 +108,25 @@ public class Selectors */ public static boolean validateSelector(String value) { + // First check if this is a combination of selectors. + if (value.contains("+")) + { + for (String selector : value.split("\\+")) + { + if (!validateSelector(selector)) + { + return false; + } + } + return true; + } + + // If this is a negation, remove the ~ and validate the rest. + if (value.startsWith("~")) + { + return validateSelector(value.substring(1)); + } + // This is a tag, which should be a resource location. if (value.startsWith("$")) { @@ -151,12 +173,45 @@ public class Selectors @SuppressWarnings({"deprecation", "removal"}) public static boolean itemMatches(ItemStack item, String selector) { - String itemResourceLocation = ForgeRegistries.ITEMS.getKey(item.getItem()).toString(); + // If this is a combination of selectors, check each one. + if (selector.contains("+")) + { + for (String subSelector : selector.split("\\+")) + { + if (!itemMatches(item, subSelector)) + { + return false; + } + } + return true; + } + + // If this is a negation, remove the ~ and check the rest. + if (selector.startsWith("~")) + { + return !itemMatches(item, selector.substring(1)); + } + + // Wildcard + if (selector.contentEquals("*")) + { + return true; + } + // Item ID + String itemResourceLocation = ForgeRegistries.ITEMS.getKey(item.getItem()).toString(); if (selector.equals(itemResourceLocation) || selector.equals(itemResourceLocation.replace("minecraft:", ""))) { return true; } + // Mod ID + else if (selector.startsWith("@")) + { + if (itemResourceLocation.startsWith(selector.substring(1) + ":")) + { + return true; + } + } // Item name color else if (selector.startsWith("#")) { @@ -174,18 +229,10 @@ public class Selectors return true; } } - // Mod ID - else if (selector.startsWith("@")) - { - if (itemResourceLocation.startsWith(selector.substring(1) + ":")) - { - return true; - } - } // Item tag else if (selector.startsWith("$")) { - Optional> matchingTag = Registry.ITEM.getTagNames().filter(tagKey -> tagKey.location().equals(new ResourceLocation(selector.substring(1)))).findFirst(); + Optional> matchingTag = BuiltInRegistries.ITEM.getTagNames().filter(tagKey -> tagKey.location().equals(new ResourceLocation(selector.substring(1)))).findFirst(); if (matchingTag.isPresent() && item.is(matchingTag.get())) { return true; diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java b/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java index db952f8..47019f5 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java @@ -10,14 +10,25 @@ import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import com.anthonyhilyard.iceberg.Loader; import com.anthonyhilyard.iceberg.events.GatherComponentsExtEvent; import com.anthonyhilyard.iceberg.events.RenderTooltipExtEvent; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.BufferUploader; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.datafixers.util.Either; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTextTooltip; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil; +import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.Rect2i; import net.minecraft.client.renderer.MultiBufferSource.BufferSource; @@ -26,23 +37,52 @@ import net.minecraft.locale.Language; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FormattedText; import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.TextColor; import net.minecraft.util.FormattedCharSequence; -import com.mojang.blaze3d.vertex.Tesselator; -import com.mojang.datafixers.util.Either; - import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemStack; -import com.mojang.math.Matrix4f; import net.minecraftforge.client.ForgeHooksClient; import net.minecraftforge.client.event.RenderTooltipEvent; +import net.minecraftforge.client.event.RegisterClientTooltipComponentFactoriesEvent; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.joml.Matrix4f; public class Tooltips { + public record TooltipColors(TextColor backgroundColorStart, TextColor backgroundColorEnd, TextColor borderColorStart, TextColor borderColorEnd) {} + private static final TooltipColors DEFAULT_COLORS = new TooltipColors(TextColor.fromRgb(0xF0100010), TextColor.fromRgb(0xF0100010), TextColor.fromRgb(0x505000FF), TextColor.fromRgb(0x5028007F)); + private static final FormattedCharSequence SPACE = FormattedCharSequence.forward(" ", Style.EMPTY); private static ItemRenderer itemRenderer = null; + private static boolean tooltipWidthWarningShown = false; + + public static TooltipColors currentColors = DEFAULT_COLORS; + + public static class TitleBreakComponent implements TooltipComponent, ClientTooltipComponent + { + @Override + public int getHeight() { return 0; } + + @Override + public int getWidth(Font font) { return 0; } + + public static void registerFactory() + { + FMLJavaModLoadingContext.get().getModEventBus().addListener(TitleBreakComponent::onRegisterTooltipEvent); + } + + private static void onRegisterTooltipEvent(RegisterClientTooltipComponentFactoriesEvent event) + { + event.register(TitleBreakComponent.class, x -> x); + } + } + + public static interface InlineComponent { } public static class TooltipInfo { @@ -51,10 +91,12 @@ public class Tooltips private Font font; private List components = new ArrayList<>(); - public TooltipInfo(List components, Font font) + public TooltipInfo(List components, Font font, int titleLines) { this.components = components; this.font = font; + this.titleLines = titleLines; + this.tooltipWidth = getMaxLineWidth(); } public int getTooltipWidth() { return tooltipWidth; } @@ -85,6 +127,39 @@ public class Tooltips } } + public static int calculateTitleLines(List components) + { + if (components == null || components.isEmpty()) + { + return 0; + } + + // Determine the number of "title lines". This will be the number of text components before the first TitleBreakComponent. + // If for some reason there is no TitleBreakComponent, we'll default to 1. + int titleLines = 0; + boolean foundTitleBreak = false; + for (ClientTooltipComponent component : components) + { + if (component instanceof ClientTextTooltip) + { + titleLines++; + } + else if (component instanceof TitleBreakComponent) + { + foundTitleBreak = true; + break; + } + } + + // We didn't find a title break (shouldn't happen normally), so default to 1. + if (!foundTitleBreak) + { + titleLines = 1; + } + + return titleLines; + } + public static void renderItemTooltip(@Nonnull final ItemStack stack, PoseStack poseStack, TooltipInfo info, Rect2i rect, int screenWidth, int screenHeight, int backgroundColor, int borderColorStart, int borderColorEnd) @@ -125,7 +200,7 @@ public class Tooltips // Center the title now if needed. if (centeredTitle) { - info = new TooltipInfo(centerTitle(info.getComponents(), info.getFont(), rect.getWidth()), info.getFont()); + info = new TooltipInfo(centerTitle(info.getComponents(), info.getFont(), info.getMaxLineWidth(), info.getTitleLines()), info.getFont(), info.getTitleLines()); } int rectX = rect.getX() + 4; @@ -148,7 +223,12 @@ public class Tooltips final int zLevel = 400; float f = itemRenderer.blitOffset; itemRenderer.blitOffset = zLevel; - Matrix4f mat = poseStack.last().pose(); + + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder bufferbuilder = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + Matrix4f matrix4f = poseStack.last().pose(); RenderTooltipExtEvent.Color colorEvent = new RenderTooltipExtEvent.Color(stack, poseStack, rectX, rectY, info.getFont(), backgroundColorStart, backgroundColorEnd, borderColorStart, borderColorEnd, info.getComponents(), comparison, index); MinecraftForge.EVENT_BUS.post(colorEvent); @@ -158,35 +238,41 @@ public class Tooltips borderColorStart = colorEvent.getBorderStart(); borderColorEnd = colorEvent.getBorderEnd(); - GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY - 4, rectX + rect.getWidth() + 3, rectY - 3, backgroundColorStart, backgroundColorStart); - GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY + rect.getHeight() + 3, rectX + rect.getWidth() + 3, rectY + rect.getHeight() + 4, backgroundColorEnd, backgroundColorEnd); - GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY - 3, rectX + rect.getWidth() + 3, rectY + rect.getHeight() + 3, backgroundColorStart, backgroundColorEnd); - GuiHelper.drawGradientRect(mat, zLevel, rectX - 4, rectY - 3, rectX - 3, rectY + rect.getHeight() + 3, backgroundColorStart, backgroundColorEnd); - GuiHelper.drawGradientRect(mat, zLevel, rectX + rect.getWidth() + 3, rectY - 3, rectX + rect.getWidth() + 4, rectY + rect.getHeight() + 3, backgroundColorStart, backgroundColorEnd); - GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY - 3 + 1, rectX - 3 + 1, rectY + rect.getHeight() + 3 - 1, borderColorStart, borderColorEnd); - GuiHelper.drawGradientRect(mat, zLevel, rectX + rect.getWidth() + 2, rectY - 3 + 1, rectX + rect.getWidth() + 3, rectY + rect.getHeight() + 3 - 1, borderColorStart, borderColorEnd); - GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY - 3, rectX + rect.getWidth() + 3, rectY - 3 + 1, borderColorStart, borderColorStart); - GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY + rect.getHeight() + 2, rectX + rect.getWidth() + 3, rectY + rect.getHeight() + 3, borderColorEnd, borderColorEnd); + currentColors = new TooltipColors(TextColor.fromRgb(backgroundColorStart), TextColor.fromRgb(backgroundColorEnd), TextColor.fromRgb(borderColorStart), TextColor.fromRgb(borderColorEnd)); + + TooltipRenderUtil.renderTooltipBackground((matrix, bufferBuilder, left, top, right, bottom, z, startColor, endColor) -> { + GuiHelper.drawGradientRect(matrix, bufferBuilder, left, top, right, bottom, z, startColor, endColor); + }, matrix4f, bufferbuilder, rectX, rectY, rect.getWidth(), rect.getHeight(), zLevel); - BufferSource renderType = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder()); - poseStack.translate(0.0D, 0.0D, zLevel); + RenderSystem.enableDepthTest(); + RenderSystem.disableTexture(); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + BufferUploader.drawWithShader(bufferbuilder.end()); + RenderSystem.disableBlend(); + RenderSystem.enableTexture(); + BufferSource bufferSource = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder()); + poseStack.translate(0.0f, 0.0f, zLevel); int tooltipTop = rectY; - boolean titleRendered = false; + int titleLines = info.getTitleLines(); for (int componentNumber = 0; componentNumber < info.getComponents().size(); ++componentNumber) { ClientTooltipComponent textComponent = info.getComponents().get(componentNumber); - textComponent.renderText(preEvent.getFont(), rectX, tooltipTop, mat, renderType); + textComponent.renderText(preEvent.getFont(), rectX, tooltipTop, matrix4f, bufferSource); tooltipTop += textComponent.getHeight(); - if (!titleRendered && textComponent instanceof ClientTextTooltip) + if ((textComponent instanceof ClientTextTooltip || textComponent instanceof InlineComponent) && titleLines > 0) { - tooltipTop += 2; - titleRendered = true; + titleLines -= (textComponent instanceof InlineComponent) ? 2 : 1; + if (titleLines <= 0) + { + tooltipTop += 2; + } } } - renderType.endBatch(); + bufferSource.endBatch(); poseStack.popPose(); tooltipTop = rectY; @@ -226,7 +312,22 @@ public class Tooltips } int tooltipTextWidth = event.getTooltipElements().stream() - .mapToInt(either -> either.map(font::width, component -> 0)) + .mapToInt(either -> either.map(component -> { + try + { + return font.width(component); + } + catch (Exception e) + { + // Log this exception, but only once. + if (!tooltipWidthWarningShown) + { + Loader.LOGGER.error("Error rendering tooltip component: \n" + ExceptionUtils.getStackTrace(e)); + tooltipWidthWarningShown = true; + } + return 0; + } + }, component -> 0)) .max() .orElse(0); @@ -239,9 +340,13 @@ public class Tooltips if (tooltipX < 4) // if the tooltip doesn't fit on the screen { if (mouseX > screenWidth / 2) + { tooltipTextWidth = mouseX - 12 - 8; + } else + { tooltipTextWidth = screenWidth - 16 - mouseX; + } needsWrap = true; } } @@ -300,12 +405,24 @@ public class Tooltips int tooltipTextWidth = minWidth; int tooltipHeight = components.size() == 1 ? -2 : 0; + int titleLines = calculateTitleLines(components); if (centeredTitle) { - components = centerTitle(components, font, minWidth); + // Calculate the current tooltip width prior to centering. + for (ClientTooltipComponent component : components) + { + int componentWidth = component.getWidth(event.getFont()); + if (componentWidth > tooltipTextWidth) + { + tooltipTextWidth = componentWidth; + } + } + components = centerTitle(components, font, tooltipTextWidth, titleLines); } + tooltipTextWidth = minWidth; + for (ClientTooltipComponent component : components) { int componentWidth = component.getWidth(event.getFont()); @@ -333,25 +450,20 @@ public class Tooltips return rect; } - public static List centerTitle(List components, Font font, int minWidth) + public static List centerTitle(List components, Font font, int width) { - // Calculate tooltip width first. - int tooltipWidth = minWidth; + return centerTitle(components, font, width, 1); + } - for (ClientTooltipComponent clienttooltipcomponent : components) + public static List centerTitle(List components, Font font, int width, int titleLines) + { + List result = new ArrayList<>(components); + + if (components.isEmpty() || titleLines <= 0 || titleLines >= components.size()) { - if (clienttooltipcomponent == null) - { - return components; - } - int componentWidth = clienttooltipcomponent.getWidth(font); - if (componentWidth > tooltipWidth) - { - tooltipWidth = componentWidth; - } + return result; } - // TODO: If the title is multiple lines, we need to extend this for each one. // Find the title component, which is the first text component. int titleIndex = 0; for (ClientTooltipComponent clienttooltipcomponent : components) @@ -363,29 +475,30 @@ public class Tooltips titleIndex++; } - if (titleIndex >= components.size()) + for (int i = 0; i < titleLines; i++) { - titleIndex = 0; - } + ClientTooltipComponent titleComponent = components.get(titleIndex + i); - List recomposedLines = StringRecomposer.recompose(List.of(components.get(titleIndex))); - if (recomposedLines.isEmpty()) - { - return components; - } + if (titleComponent != null) + { + List recomposedLines = StringRecomposer.recompose(List.of(titleComponent)); + if (recomposedLines.isEmpty()) + { + return components; + } - List result = new ArrayList<>(components); + FormattedCharSequence title = Language.getInstance().getVisualOrder(recomposedLines.get(0)); - FormattedCharSequence title = Language.getInstance().getVisualOrder(recomposedLines.get(0)); - while (result.get(titleIndex).getWidth(font) < tooltipWidth) - { - title = FormattedCharSequence.fromList(List.of(SPACE, title, SPACE)); - if (title == null) - { - break; + while (ClientTooltipComponent.create(title).getWidth(font) < width) + { + title = FormattedCharSequence.fromList(List.of(SPACE, title, SPACE)); + if (title == null) + { + break; + } + } + result.set(titleIndex + i, ClientTooltipComponent.create(title)); } - - result.set(titleIndex, ClientTooltipComponent.create(title)); } return result; } diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index a58652d..04c2f3f 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -1,2 +1,8 @@ public net.minecraft.client.gui.screens.inventory.tooltip.ClientTextTooltip f_169936_ # text -protected net.minecraftforge.common.ForgeConfigSpec (Lcom/electronwill/nightconfig/core/UnmodifiableConfig;Lcom/electronwill/nightconfig/core/UnmodifiableConfig;Ljava/util/Map;)V # constructor \ No newline at end of file +protected net.minecraftforge.common.ForgeConfigSpec (Lcom/electronwill/nightconfig/core/UnmodifiableConfig;Lcom/electronwill/nightconfig/core/UnmodifiableConfig;Ljava/util/Map;)V # constructor +public net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil f_262744_ # BACKGROUND_COLOR +public net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil f_262725_ # BORDER_COLOR_TOP +public net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil f_262743_ # BORDER_COLOR_BOTTOM +public net.minecraft.world.item.MinecartItem f_42935_ # type +public net.minecraft.world.item.BoatItem f_40616_ # type +public net.minecraft.world.item.BoatItem f_220011_ # hasChest diff --git a/src/main/resources/iceberg.mixins.json b/src/main/resources/iceberg.mixins.json index 8acd6e0..c272c92 100644 --- a/src/main/resources/iceberg.mixins.json +++ b/src/main/resources/iceberg.mixins.json @@ -11,7 +11,8 @@ "ScreenMixin", "ClientPacketListenerMixin", "TextColorMixin", - "MinecraftMixin" + "MinecraftMixin", + "TooltipRenderUtilMixin" ], "server": [ "MainMixin" -- cgit