diff options
Diffstat (limited to 'src/main')
14 files changed, 1339 insertions, 251 deletions
diff --git a/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java b/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java index 8218f71..8e3c31b 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java +++ b/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java @@ -1,12 +1,26 @@ package com.anthonyhilyard.iceberg; +import java.util.List; + +import com.anthonyhilyard.iceberg.events.RenderTooltipEvents; +import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.GatherResult; +import com.anthonyhilyard.iceberg.util.Tooltips.TitleBreakComponent; +import com.mojang.datafixers.util.Either; + import net.fabricmc.api.ClientModInitializer; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.inventory.tooltip.TooltipComponent; +import net.minecraft.world.item.ItemStack; public class IcebergClient implements ClientModInitializer { @Override public void onInitializeClient() { + TitleBreakComponent.registerFactory(); + RenderTooltipEvents.GATHER.register(IcebergClient::onGatherComponentsEventEnd); + // Event testing. // CriterionCallback.EVENT.register((player, advancement, criterion) -> { Loader.LOGGER.info("CriterionCallback: {}, {}, {}", player.getName().getString(), advancement.getId().toString(), criterion); }); // NewItemPickupCallback.EVENT.register((uuid, itemStack) -> { Loader.LOGGER.info("NewItemPickupCallback: {}, {}", uuid.toString(), itemStack.getDisplayName().getString()); }); @@ -23,4 +37,22 @@ public class IcebergClient implements ClientModInitializer // Loader.LOGGER.info("RenderTooltipEvents.POST: {}, {}, {}, {}, {}, {}, {}, {}, {}", stack.getDisplayName().getString(), components.stream().map(Object::toString).collect(Collectors.joining()), poseStack, x, y, font, width, height, comparison); // }); } + + public static GatherResult onGatherComponentsEventEnd(ItemStack itemStack, int screenWidth, int screenHeight, List<Either<FormattedText, TooltipComponent>> tooltipElements, int maxWidth, int index) + { + if (tooltipElements.size() > 1) + { + // Insert a title break component after the first formattedText component. + for (int i = 0; i < tooltipElements.size(); i++) + { + if (tooltipElements.get(i).left().isPresent()) + { + tooltipElements.add(i + 1, Either.<FormattedText, TooltipComponent>right(new TitleBreakComponent())); + break; + } + } + } + + return new GatherResult(InteractionResult.PASS, maxWidth, tooltipElements); + } } diff --git a/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java b/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java index 622d3e0..c5a10a8 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java +++ b/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java @@ -23,7 +23,7 @@ public final class RenderTooltipEvents GatherResult result = new GatherResult(InteractionResult.PASS, maxWidth, tooltipElements); for (RenderTooltipEvents.Gather callback : callbacks) { - result = callback.onGather(itemStack, screenWidth, screenHeight, tooltipElements, maxWidth, index); + result = callback.onGather(itemStack, screenWidth, screenHeight, result.tooltipElements, result.maxWidth, index); if (result.result != InteractionResult.PASS) { @@ -38,7 +38,7 @@ public final class RenderTooltipEvents PreExtResult result = new PreExtResult(InteractionResult.PASS, x, y, screenWidth, screenHeight, font); for (RenderTooltipEvents.PreExt callback : callbacks) { - result = callback.onPre(stack, components, poseStack, x, y, screenWidth, screenHeight, font, comparison, index); + result = callback.onPre(stack, components, poseStack, result.x, result.y, screenWidth, screenHeight, result.font, comparison, index); if (result.result != InteractionResult.PASS) { diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java index a797c9f..bc3674f 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java +++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java @@ -9,16 +9,12 @@ import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.ColorResult; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.PreExtResult; import com.anthonyhilyard.iceberg.util.Tooltips; import com.google.common.collect.Lists; -import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.Tesselator; -import com.mojang.math.Matrix4f; 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.Redirect; import org.spongepowered.asm.mixin.injection.At.Shift; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; @@ -29,9 +25,11 @@ 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.network.chat.Component; import net.minecraft.world.InteractionResult; -import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.Slot; import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemStack; @@ -60,12 +58,24 @@ public class ScreenMixin extends AbstractContainerEventHandler } @Inject(method = "renderTooltip(Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;Ljava/util/Optional;II)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;renderTooltipInternal(Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;II)V"), - locals = LocalCapture.CAPTURE_FAILEXCEPTION) + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;renderTooltipInternal(Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;IILnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;)V", + shift = Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILEXCEPTION) public void renderTooltip(PoseStack poseStack, List<Component> textComponents, Optional<TooltipComponent> itemComponent, int x, int y, CallbackInfo info, List<ClientTooltipComponent> components) { Screen self = (Screen)(Object)this; + if (self instanceof AbstractContainerScreen<?> containerScreen) + { + Slot hoveredSlot = containerScreen.hoveredSlot; + + // 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. + if (tooltipStack.isEmpty() && hoveredSlot != null) + { + tooltipStack = hoveredSlot.getItem(); + } + } + List<ClientTooltipComponent> newComponents = Tooltips.gatherTooltipComponents(tooltipStack, textComponents, itemComponent, x, self.width, self.height, null, font, -1); if (newComponents != null && !newComponents.isEmpty()) { @@ -74,93 +84,65 @@ public class ScreenMixin extends AbstractContainerEventHandler } } - @SuppressWarnings({"unchecked", "deprecation"}) + @SuppressWarnings("deprecation") @Inject(method = "renderTooltipInternal", at = @At(value = "HEAD"), cancellable = true) - private void preRenderTooltipInternal(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, CallbackInfo info) + private void preRenderTooltipInternal(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info) { - PreExtResult eventResult = null; Screen self = (Screen)(Object)this; - if (self instanceof AbstractContainerScreen) + + if (!components.isEmpty()) { - if (!components.isEmpty()) + PreExtResult eventResult = null; + InteractionResult result = InteractionResult.PASS; + + eventResult = RenderTooltipEvents.PREEXT.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, font, false, 0); + result = eventResult.result(); + + if (result != InteractionResult.PASS) { - Slot hoveredSlot = ((AbstractContainerScreen<AbstractContainerMenu>)self).hoveredSlot; - if (hoveredSlot != null) - { - ItemStack tooltipStack = hoveredSlot.getItem(); - InteractionResult result = InteractionResult.PASS; - eventResult = RenderTooltipEvents.PREEXT.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, font, false, 0); - result = eventResult.result(); - - if (result != InteractionResult.PASS) - { - info.cancel(); - } - - // Fire a pre event as well for compatibility. - result = RenderTooltipEvents.PRE.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, -1, font, false); - if (result != InteractionResult.PASS) - { - info.cancel(); - } - } + info.cancel(); } - } - } - @SuppressWarnings("unchecked") - @Redirect(method = "renderTooltipInternal", at = @At(value = "INVOKE", - target = "Lnet/minecraft/client/gui/screens/Screen;fillGradient(Lcom/mojang/math/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIIIII)V")) - private void fillGradientProxy(Matrix4f matrix4f, BufferBuilder bufferBuilder, int left, int top, int right, int bottom, int zIndex, int colorStart, int colorEnd) - { - Screen self = (Screen)(Object)this; - ItemStack tooltipStack = ItemStack.EMPTY; - if (self instanceof AbstractContainerScreen) - { - Slot hoveredSlot = ((AbstractContainerScreen<AbstractContainerMenu>)self).hoveredSlot; - - if (hoveredSlot != null) + // Fire a pre event as well for compatibility. + result = RenderTooltipEvents.PRE.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, -1, font, false); + if (result != InteractionResult.PASS) { - tooltipStack = hoveredSlot.getItem(); + info.cancel(); } } - if (tooltipStack == ItemStack.EMPTY) - { - // Do standard functionality if this isn't a container screen. - Screen.fillGradient(matrix4f, bufferBuilder, left, top, right, bottom, zIndex, colorStart, colorEnd); - } - else - { - // Otherwise do nothing to disable the default calls. - } } - @SuppressWarnings({"unchecked", "deprecation"}) - @Inject(method = "renderTooltipInternal", at = @At(value = "INVOKE", - target = "Lnet/minecraft/client/gui/screens/Screen;fillGradient(Lcom/mojang/math/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIIIII)V", ordinal = 0, shift = Shift.BEFORE), - locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void preFillGradient(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, CallbackInfo info, - int __, int ___, int left, int top, int width, int height, int background, int borderStart, int borderEnd, - int zIndex, float blitOffset, Tesselator tesselator, BufferBuilder bufferBuilder, Matrix4f matrix4f) + @SuppressWarnings("deprecation") + @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)) + private void preFillGradient(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info) { Screen self = (Screen)(Object)this; - ItemStack tooltipStack = ItemStack.EMPTY; - if (self instanceof AbstractContainerScreen) + ItemStack containerStack = ItemStack.EMPTY; + if (self instanceof AbstractContainerScreen<?> containerScreen) { - Slot hoveredSlot = ((AbstractContainerScreen<AbstractContainerMenu>)self).hoveredSlot; - + Slot hoveredSlot = containerScreen.hoveredSlot; if (hoveredSlot != null) { - tooltipStack = hoveredSlot.getItem(); + containerStack = hoveredSlot.getItem(); } } - if (tooltipStack != ItemStack.EMPTY) + if (containerStack.isEmpty()) { + containerStack = tooltipStack; + } + + if (!containerStack.isEmpty()) + { + int background = TooltipRenderUtil.BACKGROUND_COLOR; int backgroundEnd = background; + int borderStart = TooltipRenderUtil.BORDER_COLOR_TOP; + int borderEnd = TooltipRenderUtil.BORDER_COLOR_BOTTOM; // Do colors now, sure why not. - ColorExtResult result = RenderTooltipEvents.COLOREXT.invoker().onColor(tooltipStack, components, poseStack, x, y, font, background, backgroundEnd, borderStart, borderEnd, false, 0); + ColorExtResult result = RenderTooltipEvents.COLOREXT.invoker().onColor(containerStack, components, poseStack, x, y, font, background, backgroundEnd, borderStart, borderEnd, false, 0); if (result != null) { background = result.backgroundStart(); @@ -170,7 +152,7 @@ public class ScreenMixin extends AbstractContainerEventHandler } // Fire a colors event as well for compatibility. - ColorResult colorResult = RenderTooltipEvents.COLOR.invoker().onColor(tooltipStack, components, poseStack, x, y, font, background, borderStart, borderEnd, false); + ColorResult colorResult = RenderTooltipEvents.COLOR.invoker().onColor(containerStack, components, poseStack, x, y, font, background, borderStart, borderEnd, false); if (colorResult != null) { background = colorResult.background(); @@ -178,35 +160,37 @@ public class ScreenMixin extends AbstractContainerEventHandler borderEnd = colorResult.borderEnd(); } - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 4, left + width + 3, top - 3, zIndex, background, background); - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top + height + 3, left + width + 3, top + height + 4, zIndex, backgroundEnd, backgroundEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 3, left + width + 3, top + height + 3, zIndex, background, backgroundEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left - 4, top - 3, left - 3, top + height + 3, zIndex, background, backgroundEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left + width + 3, top - 3, left + width + 4, top + height + 3, zIndex, background, backgroundEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 3 + 1, left - 3 + 1, top + height + 3 - 1, zIndex, borderStart, borderEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left + width + 2, top - 3 + 1, left + width + 3, top + height + 3 - 1, zIndex, borderStart, borderEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 3, left + width + 3, top - 3 + 1, zIndex, borderStart, borderStart); - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top + height + 2, left + width + 3, top + height + 3, zIndex, borderEnd, borderEnd); + Tooltips.currentColors = new Tooltips.TooltipColors(TextColor.fromRgb(background), TextColor.fromRgb(backgroundEnd), TextColor.fromRgb(borderStart), TextColor.fromRgb(borderEnd)); } } - @SuppressWarnings({"unchecked", "deprecation"}) + @SuppressWarnings("deprecation") @Inject(method = "renderTooltipInternal", at = @At(value = "TAIL"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void renderTooltipInternal(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, CallbackInfo info, int tooltipWidth, int tooltipHeight, int postX, int postY) + private void renderTooltipInternal(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info, int tooltipWidth, int tooltipHeight, int postX, int postY) { - if ((Screen)(Object)this instanceof AbstractContainerScreen) + Screen self = (Screen)(Object)this; + ItemStack containerStack = ItemStack.EMPTY; + if (self instanceof AbstractContainerScreen<?> containerScreen) { - if (!components.isEmpty()) + Slot hoveredSlot = containerScreen.hoveredSlot; + if (hoveredSlot != null) { - Slot hoveredSlot = ((AbstractContainerScreen<AbstractContainerMenu>)(Object)this).hoveredSlot; - if (hoveredSlot != null) - { - ItemStack tooltipStack = hoveredSlot.getItem(); - RenderTooltipEvents.POSTEXT.invoker().onPost(tooltipStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false, 0); - RenderTooltipEvents.POST.invoker().onPost(tooltipStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false); - } + containerStack = hoveredSlot.getItem(); } } + + if (containerStack.isEmpty()) + { + containerStack = tooltipStack; + } + + if (!containerStack.isEmpty() && !components.isEmpty()) + { + RenderTooltipEvents.POSTEXT.invoker().onPost(containerStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false, 0); + RenderTooltipEvents.POST.invoker().onPost(containerStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false); + } + + tooltipStack = ItemStack.EMPTY; } @Override 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..18ef9e4 --- /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(); + } + } +}
\ No newline at end of file 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..b8f5fde --- /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; + } +}
\ No newline at end of file diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java index b77bb09..c3cc4b5 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java +++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java @@ -1,50 +1,122 @@ 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.ItemBlockRenderTypes; +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.block.model.ItemTransforms.TransformType; +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.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.packs.resources.ReloadableResourceManager; +import net.minecraft.server.packs.resources.ResourceManager; +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; + /** - * 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<Item, CompoundTag> cachedArmorStandItem = null; + private static Pair<Item, CompoundTag> cachedHorseArmorItem = null; + private static Map<Item, ModelBounds> modelBoundsCache = Maps.newHashMap(); + + private static final List<Direction> 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; + if (mc.getResourceManager() instanceof ReloadableResourceManager resourceManager) + { + resourceManager.registerReloadListener(this); + } // Initialize the icon framebuffer if needed. if (iconFrameBuffer == null) @@ -56,6 +128,515 @@ 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 <T extends MultiBufferSource> void renderModelInternal(ItemStack itemStack, ItemTransforms.TransformType transformType, boolean leftHanded, PoseStack poseStack, + Quaternionf rotation, T bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel, + Predicate<T> 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)) + { + blockEntityRenderer.renderByItem(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay); + } + } + + private void renderModel(ItemStack itemStack, TransformType transformType, boolean leftHanded, PoseStack poseStack, Quaternionf rotation, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel) + { + if (!itemStack.isEmpty()) + { + // If this model doesn't have a special transform for the given transform type (most likely GUI), default to first-person right handed instead. + TransformType previewTransform = transformType; + if (!bakedModel.getTransforms().hasTransform(transformType)) + { + previewTransform = TransformType.GROUND; + } + + 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 + { + bakedModel.getTransforms().getTransform(previewTransform).apply(leftHanded, poseStack); + } + + // Get the model bounds. + ModelBounds modelBounds = getModelBounds(itemStack, previewTransform, 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.getTransforms().getTransform(previewTransform).apply(leftHanded, poseStack); + } + + CheckedBufferSource checkedBufferSource = new CheckedBufferSource(bufferSource); + renderModelInternal(itemStack, previewTransform, 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<BlockEntity> 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 : List.of(bakedModel)) + { + for (var rendertype : List.of(ItemBlockRenderTypes.getRenderType(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); + } + + // 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<Vector3f> 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(BuiltInRegistries.ITEM.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,33 +646,42 @@ 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)); - - Lighting.setupFor3DItems(); + RenderSystem.setProjectionMatrix(matrix); mc.getTextureManager().getTexture(InventoryMenu.BLOCK_ATLAS).setFilter(false, false); RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS); + RenderSystem.disableCull(); 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 = mc.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(); @@ -108,13 +698,12 @@ public class CustomItemRenderer extends ItemRenderer RenderSystem.disableCull(); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, alpha); modelViewStack.pushPose(); - modelViewStack.scale(1.0f, -1.0f, 1.0f); modelViewStack.translate(0.0f, 0.0f, 50.0f + this.blitOffset); RenderSystem.applyModelViewMatrix(); RenderSystem.setShaderTexture(0, iconFrameBuffer.getColorTextureId()); - GuiComponent.blit(new PoseStack(), x, y - 18, 16, 16, 0, 0, iconFrameBuffer.width, iconFrameBuffer.height, iconFrameBuffer.width, iconFrameBuffer.height); + GuiComponent.blit(new PoseStack(), x, y, 16, 16, 0, 0, iconFrameBuffer.width, iconFrameBuffer.height, iconFrameBuffer.width, iconFrameBuffer.height); modelViewStack.popPose(); RenderSystem.applyModelViewMatrix(); iconFrameBuffer.unbindRead(); @@ -123,5 +712,14 @@ public class CustomItemRenderer extends ItemRenderer { iconFrameBuffer.unbindWrite(); } - } -} + } + + @Override + public void onResourceManagerReload(ResourceManager resourceManager) + { + super.onResourceManagerReload(resourceManager); + + // Clear the model bounds cache. + modelBoundsCache.clear(); + } +}
\ No newline at end of file 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..29ae995 --- /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<Vector3f> 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<Vector3f> getVertices() + { + return vertices; + } +}
\ No newline at end of file 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<DynamicResourceKey, Supplier<InputStream>> dynamicResourceMap = new HashMap<DynamicResourceKey, Supplier<InputStream>>(); + private Map<DynamicResourceKey, IoSupplier<InputStream>> dynamicResourceMap = new HashMap<DynamicResourceKey, IoSupplier<InputStream>>(); public DynamicResourcePack(String packName) { @@ -52,17 +51,17 @@ public class DynamicResourcePack implements PackResources } } - public boolean registerResource(PackType type, ResourceLocation location, Supplier<InputStream> resourceSupplier) + public boolean registerResource(PackType type, ResourceLocation location, IoSupplier<InputStream> resourceSupplier) { return register(type.getDirectory(), location.getNamespace(), location.getPath(), resourceSupplier); } - public boolean registerRootResource(String path, Supplier<InputStream> resourceSupplier) + public boolean registerRootResource(String path, IoSupplier<InputStream> resourceSupplier) { return register("root", "", path, resourceSupplier); } - private boolean register(String directory, String namespace, String path, Supplier<InputStream> resourceSupplier) + private boolean register(String directory, String namespace, String path, IoSupplier<InputStream> 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<InputStream> 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<InputStream> 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<InputStream> 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<ResourceLocation> getResources(PackType type, String namespace, String path, Predicate<ResourceLocation> 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 d3530fe..54cf422 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java @@ -1,15 +1,16 @@ package com.anthonyhilyard.iceberg.util; -import com.mojang.blaze3d.systems.RenderSystem; +import org.joml.Matrix4f; import net.minecraft.client.renderer.GameRenderer; +import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.Tesselator; 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; + 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 6a7b90d..afb5d3e 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java @@ -5,10 +5,11 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; 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; @@ -32,7 +33,9 @@ public class Selectors }}; private static Map<String, BiPredicate<Tag, String>> nbtComparators = new HashMap<String, BiPredicate<Tag, String>>() {{ - put("=", (tag, value) -> tag.getAsString().contentEquals(value)); + put("=", (tag, value) -> { + return tag.getAsString().contentEquals(value); + }); put("!=", (tag, value) -> !tag.getAsString().contentEquals(value)); @@ -83,14 +86,17 @@ public class Selectors public static List<SelectorDocumentation> 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 <tag><comparator><value> or just <tag>.", "&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 <tag><comparator><value> or just <tag>.", "&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") ); } @@ -101,6 +107,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("$")) { @@ -144,15 +169,48 @@ public class Selectors * @param selector A selector string to check against. * @return True if the item matches, false otherwise. */ - @SuppressWarnings({"removal"}) + @SuppressWarnings("removal") public static boolean itemMatches(ItemStack item, String selector) { - String itemResourceLocation = Registry.ITEM.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 = BuiltInRegistries.ITEM.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("#")) { @@ -170,18 +228,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<TagKey<Item>> matchingTag = Registry.ITEM.getTagNames().filter(tagKey -> tagKey.location().equals(new ResourceLocation(selector.substring(1)))).findFirst(); + Optional<TagKey<Item>> matchingTag = BuiltInRegistries.ITEM.getTagNames().filter(tagKey -> tagKey.location().equals(new ResourceLocation(selector.substring(1)))).findFirst(); if (matchingTag.isPresent() && item.is(matchingTag.get())) { return true; @@ -201,7 +251,7 @@ public class Selectors Minecraft mc = Minecraft.getInstance(); List<Component> lines = item.getTooltipLines(mc.player, TooltipFlag.Default.ADVANCED); String tooltipText = ""; - + // Skip title line. for (int n = 1; n < lines.size(); n++) { @@ -243,6 +293,9 @@ public class Selectors return false; } + /** + * Retrieves the first inner tag with the given key, or null if there is no match. + */ private static boolean findMatchingSubtag(Tag tag, String key, String value, BiPredicate<Tag, String> valueChecker) { if (tag == null) diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java b/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java index 57c998e..48184b9 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java @@ -6,17 +6,28 @@ import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nonnull; +import com.anthonyhilyard.iceberg.Loader; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.ColorExtResult; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.GatherResult; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.PreExtResult; +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.fabricmc.fabric.api.client.rendering.v1.TooltipComponentCallback; 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; @@ -25,20 +36,49 @@ 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.InteractionResult; import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemStack; -import com.mojang.math.Matrix4f; + +import net.fabricmc.fabric.api.client.rendering.v1.TooltipComponentCallback; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.joml.Matrix4f; public class Tooltips { - private static boolean initialized = false; + 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() + { + TooltipComponentCallback.EVENT.register(data -> { + if (data instanceof TitleBreakComponent titleBreakComponent) + { + return titleBreakComponent; + } + return null; + }); + } + } + + public static interface InlineComponent { } public static class TooltipInfo { @@ -49,11 +89,19 @@ public class Tooltips public TooltipInfo(List<ClientTooltipComponent> components, Font font) { + this(components, font, calculateTitleLines(components)); + } + + public TooltipInfo(List<ClientTooltipComponent> components, Font font, int titleLines) + { this.components = components; this.font = font; + this.titleLines = titleLines; + this.tooltipWidth = getMaxLineWidth(); } public int getTooltipWidth() { return tooltipWidth; } + public int getTooltipHeight() { return components.size() > titleLines ? components.size() * 10 + 2 : 8; } public int getTitleLines() { return titleLines; } public Font getFont() { return font; } public List<ClientTooltipComponent> getComponents() { return components; } @@ -62,65 +110,98 @@ public class Tooltips public int getMaxLineWidth() { - int width = 0; + return getMaxLineWidth(0); + } + + public int getMaxLineWidth(int minWidth) + { + int textWidth = minWidth; for (ClientTooltipComponent component : components) { - int textLineWidth = component.getWidth(font); - if (textLineWidth > width) + int componentWidth = component.getWidth(font); + if (componentWidth > textWidth) { - width = textLineWidth; + textWidth = componentWidth; } } - return width; + return textWidth; } } - private static void init(Minecraft minecraft) + public static int calculateTitleLines(List<ClientTooltipComponent> components) { - itemRenderer = minecraft.getItemRenderer(); - initialized = true; + 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(final ItemStack stack, PoseStack poseStack, TooltipInfo info, + public static void renderItemTooltip(@Nonnull final ItemStack stack, PoseStack poseStack, TooltipInfo info, Rect2i rect, int screenWidth, int screenHeight, int backgroundColor, int borderColorStart, int borderColorEnd) { renderItemTooltip(stack, poseStack, info, rect, screenWidth, screenHeight, backgroundColor, borderColorStart, borderColorEnd, false); } - public static void renderItemTooltip(final ItemStack stack, PoseStack poseStack, TooltipInfo info, + + public static void renderItemTooltip(@Nonnull final ItemStack stack, PoseStack poseStack, TooltipInfo info, Rect2i rect, int screenWidth, int screenHeight, int backgroundColor, int borderColorStart, int borderColorEnd, boolean comparison) { renderItemTooltip(stack, poseStack, info, rect, screenWidth, screenHeight, backgroundColor, borderColorStart, borderColorEnd, comparison, false); } - public static void renderItemTooltip(final ItemStack stack, PoseStack poseStack, TooltipInfo info, + public static void renderItemTooltip(@Nonnull final ItemStack stack, PoseStack poseStack, TooltipInfo info, Rect2i rect, int screenWidth, int screenHeight, int backgroundColor, int borderColorStart, int borderColorEnd, boolean comparison, boolean constrain) { renderItemTooltip(stack, poseStack, info, rect, screenWidth, screenHeight, backgroundColor, backgroundColor, borderColorStart, borderColorEnd, comparison, constrain, false, 0); } - public static void renderItemTooltip(final ItemStack stack, PoseStack poseStack, TooltipInfo info, + public static void renderItemTooltip(@Nonnull final ItemStack stack, PoseStack poseStack, TooltipInfo info, Rect2i rect, int screenWidth, int screenHeight, int backgroundColorStart, int backgroundColorEnd, int borderColorStart, int borderColorEnd, boolean comparison, boolean constrain, boolean centeredTitle, int index) { - if (info.components.isEmpty()) + if (info.getComponents().isEmpty()) { return; } - // Initialize if needed. - if (!initialized) + // Grab the itemRenderer now if needed. + if (itemRenderer == null) { - init(Minecraft.getInstance()); + itemRenderer = Minecraft.getInstance().getItemRenderer(); } // 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; @@ -141,10 +222,14 @@ public class Tooltips poseStack.pushPose(); 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(); ColorExtResult colors = RenderTooltipEvents.COLOREXT.invoker().onColor(stack, info.components, poseStack, rectX, rectY, info.getFont(), backgroundColorStart, backgroundColorEnd, borderColorStart, borderColorEnd, comparison, index); @@ -153,36 +238,48 @@ public class Tooltips borderColorStart = colors.borderStart(); borderColorEnd = colors.borderEnd(); - 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; + int titleLines = info.getTitleLines(); for (int componentNumber = 0; componentNumber < info.getComponents().size(); ++componentNumber) { - ClientTooltipComponent textComponent = (ClientTooltipComponent)info.getComponents().get(componentNumber); - textComponent.renderText(info.getFont(), rectX, tooltipTop, mat, renderType); - tooltipTop += textComponent.getHeight() + (componentNumber == 0 ? 2 : 0); + ClientTooltipComponent textComponent = info.getComponents().get(componentNumber); + textComponent.renderText(preResult.font(), rectX, tooltipTop, matrix4f, bufferSource); + tooltipTop += textComponent.getHeight(); + if ((textComponent instanceof ClientTextTooltip || textComponent instanceof InlineComponent) && titleLines > 0) + { + titleLines -= (textComponent instanceof InlineComponent) ? 2 : 1; + if (titleLines <= 0) + { + tooltipTop += 2; + } + } } - renderType.endBatch(); + bufferSource.endBatch(); poseStack.popPose(); tooltipTop = rectY; for (int componentNumber = 0; componentNumber < info.getComponents().size(); ++componentNumber) { ClientTooltipComponent imageComponent = (ClientTooltipComponent)info.getComponents().get(componentNumber); - imageComponent.renderImage(info.getFont(), rectX, tooltipTop, poseStack, itemRenderer, 400); + imageComponent.renderImage(info.getFont(), rectX, tooltipTop, poseStack, itemRenderer, zLevel); tooltipTop += imageComponent.getHeight() + (componentNumber == 0 ? 2 : 0); } @@ -206,8 +303,11 @@ public class Tooltips } // Finally, if all else fails, try casting (some mods implement it this way). - try { result = (ClientTooltipComponent)componentData; } - catch (ClassCastException e) { } + if (result == null) + { + try { result = (ClientTooltipComponent)componentData; } + catch (ClassCastException e) { } + } if (result == null) { @@ -226,7 +326,6 @@ public class Tooltips int mouseX, int screenWidth, int screenHeight, Font forcedFont, Font fallbackFont, int maxWidth, int index) { final Font font = forcedFont == null ? fallbackFont : forcedFont; - List<Either<FormattedText, TooltipComponent>> elements = textElements.stream() .map((Function<FormattedText, Either<FormattedText, TooltipComponent>>) Either::left) .collect(Collectors.toCollection(ArrayList::new)); @@ -241,8 +340,24 @@ public class Tooltips // Wrap text as needed. First get the maximum width of all components. int tooltipTextWidth = eventResult.tooltipElements().stream() - .mapToInt(either -> either.map(font::width, component -> 0)) - .max().orElse(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); boolean needsWrap = false; @@ -313,12 +428,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(font); + if (componentWidth > tooltipTextWidth) + { + tooltipTextWidth = componentWidth; + } + } + components = centerTitle(components, font, tooltipTextWidth, titleLines); } + tooltipTextWidth = minWidth; + for (ClientTooltipComponent component : components) { int componentWidth = component.getWidth(font); @@ -346,44 +473,55 @@ public class Tooltips return rect; } - public static List<ClientTooltipComponent> centerTitle(List<ClientTooltipComponent> components, Font font, int minWidth) + public static List<ClientTooltipComponent> centerTitle(List<ClientTooltipComponent> components, Font font, int width) { - // Calculate tooltip width first. - int tooltipWidth = minWidth; + return centerTitle(components, font, width, calculateTitleLines(components)); + } + public static List<ClientTooltipComponent> centerTitle(List<ClientTooltipComponent> components, Font font, int width, int titleLines) + { + List<ClientTooltipComponent> result = new ArrayList<>(components); + + if (components.isEmpty() || titleLines <= 0 || titleLines >= components.size()) + { + return result; + } + + // Find the title component, which is the first text component. + int titleIndex = 0; for (ClientTooltipComponent clienttooltipcomponent : components) { - if (clienttooltipcomponent == null) + if (clienttooltipcomponent instanceof ClientTextTooltip) { - return components; - } - int componentWidth = clienttooltipcomponent.getWidth(font); - if (componentWidth > tooltipWidth) - { - tooltipWidth = componentWidth; + break; } + titleIndex++; } - // TODO: If the title is multiple lines, we need to extend this for each one. - - List<FormattedText> recomposedLines = StringRecomposer.recompose(List.of(components.get(0))); - if (recomposedLines.isEmpty()) + for (int i = 0; i < titleLines; i++) { - return components; - } + ClientTooltipComponent titleComponent = components.get(titleIndex + i); - List<ClientTooltipComponent> result = new ArrayList<>(components); - - FormattedCharSequence title = Language.getInstance().getVisualOrder(recomposedLines.get(0)); - FormattedCharSequence space = FormattedCharSequence.forward(" ", Style.EMPTY); - while (result.get(0).getWidth(font) < tooltipWidth) - { - title = FormattedCharSequence.fromList(List.of(space, title, space)); - if (title == null) + if (titleComponent != null) { - break; + List<FormattedText> recomposedLines = StringRecomposer.recompose(List.of(titleComponent)); + if (recomposedLines.isEmpty()) + { + return components; + } + + FormattedCharSequence title = Language.getInstance().getVisualOrder(recomposedLines.get(0)); + + 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(0, ClientTooltipComponent.create(title)); } return result; } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index b7adb25..8cd7444 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -28,9 +28,9 @@ ], "depends": { - "fabricloader": ">=0.12.5", + "fabricloader": ">=0.12.8", "fabric": "*", - "minecraft": "~1.19", + "minecraft": "~1.19.3", "java": ">=17" }, "custom": { diff --git a/src/main/resources/iceberg.accesswidener b/src/main/resources/iceberg.accesswidener index 144309a..2c544b7 100644 --- a/src/main/resources/iceberg.accesswidener +++ b/src/main/resources/iceberg.accesswidener @@ -1,3 +1,10 @@ accessWidener v1 named accessible field net/minecraft/client/gui/screens/inventory/tooltip/ClientTextTooltip text Lnet/minecraft/util/FormattedCharSequence; -accessible field net/minecraft/client/gui/screens/inventory/AbstractContainerScreen hoveredSlot Lnet/minecraft/world/inventory/Slot;
\ No newline at end of file +accessible field net/minecraft/client/gui/screens/inventory/AbstractContainerScreen hoveredSlot Lnet/minecraft/world/inventory/Slot; +accessible field net/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil BACKGROUND_COLOR I +accessible field net/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil BORDER_COLOR_TOP I +accessible field net/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil BORDER_COLOR_BOTTOM I +accessible field net/minecraft/world/item/MinecartItem type Lnet/minecraft/world/entity/vehicle/AbstractMinecart$Type; +accessible field net/minecraft/world/item/BoatItem type Lnet/minecraft/world/entity/vehicle/Boat$Type; +accessible field net/minecraft/world/item/BoatItem hasChest Z +accessible method net/minecraft/client/renderer/entity/ItemRenderer renderModelLists (Lnet/minecraft/client/resources/model/BakedModel;Lnet/minecraft/world/item/ItemStack;IILcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;)V
\ No newline at end of file diff --git a/src/main/resources/iceberg.mixins.json b/src/main/resources/iceberg.mixins.json index ed51f10..f3f0e24 100644 --- a/src/main/resources/iceberg.mixins.json +++ b/src/main/resources/iceberg.mixins.json @@ -9,12 +9,13 @@ "ScreenMixin", "ClientPacketListenerMixin", "LivingEntityMixin", + "TextColorMixin", "MinecraftMixin", - "TextColorMixin" + "TooltipRenderUtilMixin" ], "injectors": { "defaultRequire": 1 }, - "minVersion": "0.8.4", + "minVersion": "0.8.5", "target": "@env(DEFAULT)" } |