aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java32
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java4
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java166
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java120
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java68
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java656
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java79
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/DynamicResourcePack.java59
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java5
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java97
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java286
-rw-r--r--src/main/resources/fabric.mod.json4
-rw-r--r--src/main/resources/iceberg.accesswidener9
-rw-r--r--src/main/resources/iceberg.mixins.json5
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)"
}