aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAnthony Hilyard <anthony.hilyard@gmail.com>2022-12-22 15:20:04 -0800
committerAnthony Hilyard <anthony.hilyard@gmail.com>2022-12-22 15:20:04 -0800
commitce46c0feb25b6127a1ce4e5624a05acf5ce16a71 (patch)
treedf65f661c8dc001838d15979a5316fe5db0935eb /src
parent379e47c13645498bc9fe3c810bef4bba5994504b (diff)
downloadIceberg-ce46c0feb25b6127a1ce4e5624a05acf5ce16a71.tar.gz
Iceberg-ce46c0feb25b6127a1ce4e5624a05acf5ce16a71.tar.bz2
Iceberg-ce46c0feb25b6127a1ce4e5624a05acf5ce16a71.zip
Added new selectors and item rendering capability.
- Added wildcard item selector. - Added selector negation. - Added selector combination. - Item detail models can now be rendered via the custom item renderer. - Fixed several tooltip rendering bugs. - Added Minecraft 1.19.3 support.
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java26
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/Loader.java3
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java39
-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.java617
-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.java2
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java85
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java229
-rw-r--r--src/main/resources/META-INF/accesstransformer.cfg8
-rw-r--r--src/main/resources/iceberg.mixins.json3
13 files changed, 1205 insertions, 133 deletions
diff --git a/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java b/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java
index edbfc1b..a5bd0dd 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java
@@ -1,6 +1,14 @@
package com.anthonyhilyard.iceberg;
+import com.anthonyhilyard.iceberg.util.Tooltips.TitleBreakComponent;
+import com.mojang.datafixers.util.Either;
+
+import net.minecraft.network.chat.FormattedText;
+import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.client.event.RenderTooltipEvent.GatherComponents;
+import net.minecraftforge.eventbus.api.EventPriority;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
@@ -14,7 +22,23 @@ public class IcebergClient
public void onClientSetup(FMLClientSetupEvent event)
{
-
+ }
+
+ @SubscribeEvent(priority = EventPriority.LOWEST)
+ public static void onGatherComponentsEventEnd(GatherComponents event)
+ {
+ if (event.getTooltipElements().size() > 1)
+ {
+ // Insert a title break component after the first formattedText component.
+ for (int i = 0; i < event.getTooltipElements().size(); i++)
+ {
+ if (event.getTooltipElements().get(i).left().isPresent())
+ {
+ event.getTooltipElements().add(i + 1, Either.<FormattedText, TooltipComponent>right(new TitleBreakComponent()));
+ break;
+ }
+ }
+ }
}
// @SubscribeEvent
diff --git a/src/main/java/com/anthonyhilyard/iceberg/Loader.java b/src/main/java/com/anthonyhilyard/iceberg/Loader.java
index ae7db63..8d6ceb4 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/Loader.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/Loader.java
@@ -3,6 +3,8 @@ package com.anthonyhilyard.iceberg;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import com.anthonyhilyard.iceberg.util.Tooltips.TitleBreakComponent;
+
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
@@ -23,6 +25,7 @@ public class Loader
{
if (FMLEnvironment.dist == Dist.CLIENT)
{
+ TitleBreakComponent.registerFactory();
IcebergClient mod = new IcebergClient();
MinecraftForge.EVENT_BUS.register(IcebergClient.class);
FMLJavaModLoadingContext.get().getModEventBus().addListener(mod::onClientSetup);
diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java
index 4c7e0c8..68f2e88 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java
@@ -3,6 +3,7 @@ package com.anthonyhilyard.iceberg.mixin;
import java.util.List;
import com.anthonyhilyard.iceberg.events.RenderTooltipExtEvent;
+import com.anthonyhilyard.iceberg.util.Tooltips;
import com.google.common.collect.Lists;
import com.mojang.blaze3d.vertex.PoseStack;
@@ -18,9 +19,12 @@ import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.components.events.AbstractContainerEventHandler;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
+import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner;
+import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil;
+import net.minecraft.network.chat.TextColor;
import net.minecraft.world.item.ItemStack;
-import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.event.RenderTooltipEvent;
import net.minecraftforge.common.MinecraftForge;
@@ -39,17 +43,46 @@ public class ScreenMixin extends AbstractContainerEventHandler
@Shadow
private final List<GuiEventListener> children = Lists.newArrayList();
+ @Inject(method = "renderTooltipInternal", at = @At(value = "HEAD"))
+ private void icebergRenderTooltipInternalHead(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info)
+ {
+ // If the tooltip stack is empty, try to get the stack from the slot under the mouse.
+ // This is needed for the creative inventory screen, which doesn't set the tooltip stack.
+ Screen self = (Screen) (Object) this;
+ if (tooltipStack.isEmpty() && self instanceof AbstractContainerScreen<?> containerScreen && containerScreen.getSlotUnderMouse() != null)
+ {
+ tooltipStack = containerScreen.getSlotUnderMouse().getItem();
+ }
+ }
+
+ @Inject(method = "renderTooltipInternal", at = @At(value = "TAIL"))
+ private void icebergRenderTooltipInternalTail(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info)
+ {
+ tooltipStack = ItemStack.EMPTY;
+ }
+
@Inject(method = "renderTooltipInternal",
at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/entity/ItemRenderer;blitOffset:F", ordinal = 2, shift = Shift.AFTER),
locals = LocalCapture.CAPTURE_FAILEXCEPTION)
- private void renderTooltipInternal(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, CallbackInfo info, RenderTooltipEvent.Pre pre, int tooltipWidth, int tooltipHeight, int postX, int postY)
+ private void icebergRenderTooltipInternalPost(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info, RenderTooltipEvent.Pre preEvent, int tooltipWidth, int tooltipHeight, int postX, int postY)
{
if (!components.isEmpty())
{
- MinecraftForge.EVENT_BUS.post(new RenderTooltipExtEvent.Post(tooltipStack, poseStack, postX, postY, ForgeHooksClient.getTooltipFont(tooltipFont, tooltipStack, font), tooltipWidth, tooltipHeight, components, false));
+ MinecraftForge.EVENT_BUS.post(new RenderTooltipExtEvent.Post(preEvent.getItemStack(), poseStack, postX, postY, preEvent.getFont(), tooltipWidth, tooltipHeight, components, false));
}
}
+ @Inject(method = "renderTooltipInternal",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderTooltipBackground(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V",
+ ordinal = 0, shift = Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+ private void renderTooltipInternalColor(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info, RenderTooltipEvent.Pre preEvent)
+ {
+ RenderTooltipExtEvent.Color colorEvent = new RenderTooltipExtEvent.Color(preEvent.getItemStack(), poseStack, x, y, preEvent.getFont(), TooltipRenderUtil.BACKGROUND_COLOR, TooltipRenderUtil.BACKGROUND_COLOR, TooltipRenderUtil.BORDER_COLOR_TOP, TooltipRenderUtil.BORDER_COLOR_BOTTOM, components, false, 0);
+ MinecraftForge.EVENT_BUS.post(colorEvent);
+
+ Tooltips.currentColors = new Tooltips.TooltipColors(TextColor.fromRgb(colorEvent.getBackgroundStart()), TextColor.fromRgb(colorEvent.getBackgroundEnd()), TextColor.fromRgb(colorEvent.getBorderStart()), TextColor.fromRgb(colorEvent.getBorderEnd()));
+ }
+
@Override
public List<? extends GuiEventListener> children()
{
diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java
new file mode 100644
index 0000000..791ece3
--- /dev/null
+++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java
@@ -0,0 +1,120 @@
+package com.anthonyhilyard.iceberg.mixin;
+
+import org.joml.Matrix4f;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import com.anthonyhilyard.iceberg.util.Tooltips;
+import com.mojang.blaze3d.vertex.BufferBuilder;
+
+import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil;
+import net.minecraft.network.chat.TextColor;
+
+@Mixin(TooltipRenderUtil.class)
+public class TooltipRenderUtilMixin
+{
+ @Unique
+ private static TextColor horizontalLineColor;
+
+ @Shadow
+ @Final
+ private static int BACKGROUND_COLOR;
+
+ @Shadow
+ @Final
+ private static int BORDER_COLOR_TOP;
+
+ @Shadow
+ @Final
+ private static int BORDER_COLOR_BOTTOM;
+
+ @Inject(method = "renderTooltipBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 0))
+ private static void icebergRenderTooltipBackgroundOne(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, CallbackInfo info)
+ {
+ horizontalLineColor = Tooltips.currentColors.backgroundColorStart();
+ }
+
+ @Inject(method = "renderTooltipBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 1))
+ private static void icebergRenderTooltipBackgroundTwo(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, CallbackInfo info)
+ {
+ horizontalLineColor = Tooltips.currentColors.backgroundColorEnd();
+ }
+
+ @Inject(method = "renderFrameGradient", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 0))
+ private static void icebergRenderFrameGradientOne(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, int color1, int color2, CallbackInfo info)
+ {
+ horizontalLineColor = Tooltips.currentColors.borderColorStart();
+ }
+
+ @Inject(method = "renderFrameGradient", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 1))
+ private static void icebergRenderFrameGradientTwo(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, int color1, int color2, CallbackInfo info)
+ {
+ horizontalLineColor = Tooltips.currentColors.borderColorEnd();
+ }
+
+ @Inject(method = "renderHorizontalLine", at = @At(value = "HEAD"), cancellable = true)
+ private static void icebergRenderHorizontalLine(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int z, int color, CallbackInfo info)
+ {
+ if (color != BACKGROUND_COLOR && color != BORDER_COLOR_TOP && color != BORDER_COLOR_BOTTOM)
+ {
+ // Do default behavior so other mods that change colors can still work.
+ }
+ else
+ {
+ // Replace the rendered colors with the ones previously stored.
+ int renderColor = horizontalLineColor.getValue();
+ painter.blit(matrix, bufferbuilder, x, y, x + width, y + 1, z, renderColor, renderColor);
+ info.cancel();
+ }
+ }
+
+ @Inject(method = "renderRectangle", at = @At(value = "HEAD"), cancellable = true)
+ private static void icebergRenderRectangle(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, int color, CallbackInfo info)
+ {
+ if (color != BACKGROUND_COLOR)
+ {
+ // Do default behavior so other mods that change colors can still work.
+ }
+ else
+ {
+ // Replace the rendered colors with the ones previously stored.
+ painter.blit(matrix, bufferbuilder, x, y, x + width, y + height, z, Tooltips.currentColors.backgroundColorStart().getValue(), Tooltips.currentColors.backgroundColorEnd().getValue());
+ info.cancel();
+ }
+ }
+
+ @Inject(method = "renderVerticalLine", at = @At(value = "HEAD"), cancellable = true)
+ private static void icebergRenderVerticalLine(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int height, int z, int color, CallbackInfo info)
+ {
+ if (color != BACKGROUND_COLOR)
+ {
+ // Do default behavior so other mods that change colors can still work.
+ }
+ else
+ {
+ // Replace the rendered colors with the ones previously stored.
+ painter.blit(matrix, bufferbuilder, x, y, x + 1, y + height, z, Tooltips.currentColors.backgroundColorStart().getValue(), Tooltips.currentColors.backgroundColorEnd().getValue());
+ info.cancel();
+ }
+ }
+
+ @Inject(method = "renderVerticalLineGradient", at = @At(value = "HEAD"), cancellable = true)
+ private static void icebergRenderVerticalLineGradient(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int height, int z, int startColor, int endColor, CallbackInfo info)
+ {
+ if (startColor != BORDER_COLOR_TOP || endColor != BORDER_COLOR_BOTTOM)
+ {
+ // Do default behavior so other mods that change colors can still work.
+ }
+ else
+ {
+ // Replace the rendered colors with the ones previously stored.
+ painter.blit(matrix, bufferbuilder, x, y, x + 1, y + height, z, Tooltips.currentColors.borderColorStart().getValue(), Tooltips.currentColors.borderColorEnd().getValue());
+ info.cancel();
+ }
+ }
+}
diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java
new file mode 100644
index 0000000..5d34821
--- /dev/null
+++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java
@@ -0,0 +1,68 @@
+package com.anthonyhilyard.iceberg.renderer;
+
+import com.mojang.blaze3d.vertex.VertexConsumer;
+
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.RenderType;
+
+public final class CheckedBufferSource implements MultiBufferSource
+{
+ private boolean hasRendered = false;
+ private final MultiBufferSource bufferSource;
+
+ public CheckedBufferSource(MultiBufferSource bufferSource)
+ {
+ this.bufferSource = bufferSource;
+ }
+
+ @Override
+ public VertexConsumer getBuffer(RenderType renderType)
+ {
+ final VertexConsumer vertexConsumer = bufferSource.getBuffer(renderType);
+ VertexConsumer vertexConsumerWrap = new VertexConsumer()
+ {
+ @Override
+ public VertexConsumer vertex(double x, double y, double z)
+ {
+ hasRendered = true;
+ return vertexConsumer.vertex(x, y, z);
+ }
+
+ @Override
+ public VertexConsumer color(int r, int g, int b, int a) { return vertexConsumer.color(r, g, b, a); }
+
+ @Override
+ public VertexConsumer uv(float u, float v) { return vertexConsumer.uv(u, v); }
+
+ @Override
+ public VertexConsumer overlayCoords(int x, int y) { return vertexConsumer.overlayCoords(x, y); }
+
+ @Override
+ public VertexConsumer uv2(int u, int v) { return vertexConsumer.uv2(u, v); }
+
+ @Override
+ public VertexConsumer normal(float x, float y, float z) { return vertexConsumer.normal(x, y, z); }
+
+ @Override
+ public void endVertex() { vertexConsumer.endVertex(); }
+
+ @Override
+ public void defaultColor(int r, int g, int b, int a) { vertexConsumer.defaultColor(r, g, b, a); }
+
+ @Override
+ public void unsetDefaultColor() { vertexConsumer.unsetDefaultColor(); }
+ };
+
+ return vertexConsumerWrap;
+ }
+
+ public boolean hasRendered()
+ {
+ return hasRendered;
+ }
+
+ public void reset()
+ {
+ hasRendered = false;
+ }
+}
diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java
index b77bb09..301b4c4 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java
@@ -1,50 +1,115 @@
package com.anthonyhilyard.iceberg.renderer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import com.google.common.collect.Maps;
+
+import org.joml.Matrix4f;
+import org.joml.Quaternionf;
+import org.joml.Vector3f;
+
+import org.lwjgl.opengl.GL11;
+
import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
+import com.mojang.datafixers.util.Pair;
+import com.mojang.math.MatrixUtil;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.pipeline.MainTarget;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.platform.Lighting;
+import com.mojang.blaze3d.platform.GlStateManager.SourceFactor;
+import com.mojang.blaze3d.platform.GlStateManager.DestFactor;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.lwjgl.opengl.GL11;
-
+import net.minecraft.CrashReport;
+import net.minecraft.CrashReportCategory;
+import net.minecraft.ReportedException;
import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.renderer.texture.TextureManager;
-
-import com.mojang.blaze3d.platform.GlStateManager.SourceFactor;
-import com.mojang.blaze3d.platform.GlStateManager.DestFactor;
-import com.mojang.math.Matrix4f;
-
import net.minecraft.client.color.item.ItemColors;
import net.minecraft.client.gui.GuiComponent;
import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
+import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.MultiBufferSource.BufferSource;
import net.minecraft.client.renderer.block.model.ItemTransforms;
+import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.tags.ItemTags;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.animal.horse.Horse;
+import net.minecraft.world.entity.decoration.ArmorStand;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.entity.vehicle.ChestBoat;
import net.minecraft.world.inventory.InventoryMenu;
+import net.minecraft.world.item.BlockItem;
+import net.minecraft.world.item.BoatItem;
+import net.minecraft.world.item.HorseArmorItem;
+import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.item.MinecartItem;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.EntityBlock;
+import net.minecraft.world.level.block.HalfTransparentBlock;
+import net.minecraft.world.level.block.StainedGlassPaneBlock;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.block.state.properties.BlockStateProperties;
+import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
+
+import net.minecraftforge.client.ForgeHooksClient;
+import net.minecraftforge.client.extensions.common.IClientItemExtensions;
/**
- * An extended ItemRenderer that allows items to be rendered to RenderTarget before drawing to screen.
- * This allows alpha values to be supported properly by all item types, even semi-transparent items.
+ * An extended ItemRenderer with extended functionality, such as allowing items to be rendered to a RenderTarget
+ * before drawing to screen for alpha support, and allowing handheld item models to be rendered into the gui.
*/
public class CustomItemRenderer extends ItemRenderer
{
- @SuppressWarnings("unused")
- private static final Logger LOGGER = LogManager.getLogger();
+ /* Cylindrical bounds for a model. */
+ private record ModelBounds(Vector3f center, float height, float radius) {}
private static RenderTarget iconFrameBuffer = null;
+ private static ArmorStand armorStand = null;
+ private static AbstractMinecart minecart = null;
+ private static Boat boat = null;
+ private static Horse horse = null;
+ private static Pair<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;
// Initialize the icon framebuffer if needed.
if (iconFrameBuffer == null)
@@ -56,6 +121,509 @@ public class CustomItemRenderer extends ItemRenderer
}
}
+ private void renderGuiModel(ItemStack itemStack, int x, int y, Quaternionf rotation, BakedModel bakedModel)
+ {
+ mc.getTextureManager().getTexture(InventoryMenu.BLOCK_ATLAS).setFilter(false, false);
+ RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS);
+ RenderSystem.enableBlend();
+ RenderSystem.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
+ RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
+
+ PoseStack modelViewStack = RenderSystem.getModelViewStack();
+ modelViewStack.pushPose();
+ modelViewStack.translate(x, y, 100.0f + blitOffset);
+
+ modelViewStack.translate(8.0f, 8.0f, 0.0f);
+ modelViewStack.scale(1.0f, -1.0f, 1.0f);
+ modelViewStack.scale(16.0f, 16.0f, 16.0f);
+ RenderSystem.applyModelViewMatrix();
+
+ BufferSource bufferSource = Minecraft.getInstance().renderBuffers().bufferSource();
+ boolean flatLighting = !bakedModel.usesBlockLight();
+ if (flatLighting) { Lighting.setupForFlatItems(); }
+
+ PoseStack poseStack = new PoseStack();
+ renderModel(itemStack, ItemTransforms.TransformType.GUI, false, poseStack, rotation, bufferSource, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, bakedModel);
+
+ poseStack.popPose();
+ bufferSource.endBatch();
+ RenderSystem.enableDepthTest();
+ if (flatLighting) { Lighting.setupFor3DItems(); }
+
+ modelViewStack.popPose();
+ RenderSystem.applyModelViewMatrix();
+ }
+
+ @SuppressWarnings("deprecation")
+ private void renderEntityModel(Entity entity, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight)
+ {
+ Minecraft minecraft = Minecraft.getInstance();
+ EntityRenderDispatcher entityRenderDispatcher = minecraft.getEntityRenderDispatcher();
+ Lighting.setupForEntityInInventory();
+ RenderSystem.enableDepthTest();
+ RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
+ entityRenderDispatcher.setRenderShadow(false);
+
+ RenderSystem.runAsFancy(() -> entityRenderDispatcher.render(entity, 0.0, 0.0, 0.0, 0.0f, 1.0f, poseStack, bufferSource, packedLight));
+
+ if (bufferSource instanceof BufferSource)
+ {
+ ((BufferSource)bufferSource).endBatch();
+ }
+
+ entityRenderDispatcher.setRenderShadow(true);
+
+ RenderSystem.applyModelViewMatrix();
+ Lighting.setupFor3DItems();
+ }
+
+ private <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))
+ {
+ IClientItemExtensions.of(itemStack).getCustomRenderer().renderByItem(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay);
+ }
+ }
+
+ private void renderModel(ItemStack itemStack, ItemTransforms.TransformType transformType, boolean leftHanded, PoseStack poseStack, Quaternionf rotation, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel)
+ {
+ if (!itemStack.isEmpty())
+ {
+ boolean isBlockItem = false, spawnsEntity = false, isArmor = false;
+ if (itemStack.getItem() instanceof BlockItem blockItem)
+ {
+ isBlockItem = true;
+ }
+ else if (itemStack.getItem() instanceof MinecartItem || itemStack.getItem() instanceof BoatItem)
+ {
+ spawnsEntity = true;
+ }
+
+ if (Player.getEquipmentSlotForItem(itemStack).isArmor())
+ {
+ isArmor = true;
+ }
+
+ poseStack.pushPose();
+
+ if (isBlockItem || spawnsEntity)
+ {
+ // Apply the standard block rotation so block entities match other blocks.
+ poseStack.mulPose(new Quaternionf().rotationXYZ((float)Math.toRadians(30.0f), (float)Math.toRadians(225.0f), 0.0f));
+ }
+ else
+ {
+ ForgeHooksClient.handleCameraTransforms(poseStack, bakedModel, transformType, leftHanded);
+ }
+
+ // Get the model bounds.
+ ModelBounds modelBounds = getModelBounds(itemStack, transformType, leftHanded, poseStack, rotation, bufferSource, packedLight, packedOverlay, bakedModel);
+
+ // Undo the camera transforms now that we have the model bounds.
+ poseStack.popPose();
+ poseStack.pushPose();
+
+ // Rotate the model.
+ poseStack.mulPose(rotation);
+
+ // Scale the model to fit.
+ float scale = 1.0f / Math.max(modelBounds.height, modelBounds.radius * 2.0f);
+ scale *= isBlockItem ? 0.8f : 1.0f;
+
+ // Adjust the scale based on the armor type.
+ if (isArmor)
+ {
+ switch (Player.getEquipmentSlotForItem(itemStack))
+ {
+ case HEAD:
+ scale *= 0.75f;
+ break;
+ case LEGS:
+ scale *= 1.1f;
+ break;
+ default:
+ break;
+ }
+ }
+ poseStack.scale(scale, scale, scale);
+
+ // Translate the model to the center of the item.
+ poseStack.translate(-modelBounds.center.x(), -modelBounds.center.y(), -modelBounds.center.z());
+
+ // Reapply the camera transforms.
+ if (isBlockItem || spawnsEntity)
+ {
+ // Apply the standard block rotation so block entities match other blocks.
+ poseStack.mulPose(new Quaternionf().rotationXYZ((float)Math.toRadians(30.0f), (float)Math.toRadians(225.0f), 0.0f));
+ }
+ else
+ {
+ bakedModel = ForgeHooksClient.handleCameraTransforms(poseStack, bakedModel, transformType, leftHanded);
+ }
+
+ CheckedBufferSource checkedBufferSource = new CheckedBufferSource(bufferSource);
+ renderModelInternal(itemStack, transformType, leftHanded, poseStack, rotation, checkedBufferSource, packedLight, packedOverlay, bakedModel, b -> !b.hasRendered());
+
+ poseStack.popPose();
+ }
+ }
+
+ private void renderBlockEntity(ItemStack itemStack, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight,
+ int packedOverlay, Minecraft minecraft, EntityBlock entityBlock, BlockState blockState) throws Exception
+ {
+ // If we didn't render via the BlockEntityWithoutLevelRenderer, now check if this is a block entity and render that.
+ BlockEntity blockEntity = entityBlock.newBlockEntity(BlockPos.ZERO, blockState);
+ if (blockEntity != null)
+ {
+ if (itemStack.getTag() != null)
+ {
+ blockEntity.load(itemStack.getTag());
+ }
+
+ BlockEntityRenderer<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 : bakedModel.getRenderPasses(itemStack, fabulous))
+ {
+ for (var rendertype : model.getRenderTypes(itemStack, fabulous))
+ {
+ VertexConsumer vertexconsumer;
+ if (itemStack.is(ItemTags.COMPASSES) && itemStack.hasFoil())
+ {
+ poseStack.pushPose();
+ PoseStack.Pose posestack$pose = poseStack.last();
+ if (transformType == ItemTransforms.TransformType.GUI)
+ {
+ MatrixUtil.mulComponentWise(posestack$pose.pose(), 0.5F);
+ }
+ else if (transformType.firstPerson())
+ {
+ MatrixUtil.mulComponentWise(posestack$pose.pose(), 0.75F);
+ }
+
+ if (fabulous)
+ {
+ vertexconsumer = getCompassFoilBufferDirect(bufferSource, rendertype, posestack$pose);
+ }
+ else
+ {
+ vertexconsumer = getCompassFoilBuffer(bufferSource, rendertype, posestack$pose);
+ }
+
+ poseStack.popPose();
+ }
+ else if (fabulous)
+ {
+ vertexconsumer = getFoilBufferDirect(bufferSource, rendertype, true, itemStack.hasFoil());
+ }
+ else
+ {
+ vertexconsumer = getFoilBuffer(bufferSource, rendertype, true, itemStack.hasFoil());
+ }
+
+ renderModelLists(model, itemStack, packedLight, packedOverlay, poseStack, vertexconsumer);
+ }
+ }
+ }
+
+ private boolean updateArmorStand(ItemStack itemStack)
+ {
+ EquipmentSlot equipmentSlot = Player.getEquipmentSlotForItem(itemStack);
+ if (!equipmentSlot.isArmor())
+ {
+ // This isn't armor, so don't render anything.
+ return false;
+ }
+
+ if (armorStand == null)
+ {
+ Minecraft minecraft = Minecraft.getInstance();
+ armorStand = EntityType.ARMOR_STAND.create(minecraft.level);
+ armorStand.setInvisible(true);
+ }
+
+ // If somehow the armor stand is still null, then we can't render anything.
+ if (armorStand == null)
+ {
+ return false;
+ }
+
+ // If the item has changed, then we need to update the armor stand.
+ if (cachedArmorStandItem != Pair.of(itemStack.getItem(), itemStack.getTag()))
+ {
+ // Clear the armor stand.
+ for (EquipmentSlot slot : EquipmentSlot.values())
+ {
+ armorStand.setItemSlot(slot, ItemStack.EMPTY);
+ }
+
+ // Equip the armor stand with the armor.
+ armorStand.setItemSlot(equipmentSlot, itemStack);
+
+ cachedArmorStandItem = Pair.of(itemStack.getItem(), itemStack.getTag());
+ }
+ return true;
+ }
+
+ private boolean updateMinecart(MinecartItem item)
+ {
+ if (minecart == null || minecart.getMinecartType() != item.type)
+ {
+ Minecraft minecraft = Minecraft.getInstance();
+ minecart = AbstractMinecart.createMinecart(minecraft.level, 0, 0, 0, item.type);
+ }
+
+ // If somehow the minecart is still null, then we can't render anything.
+ return minecart != null;
+ }
+
+ private boolean updateBoat(BoatItem item)
+ {
+ if (boat == null || boat.getVariant() != item.type || (boat instanceof ChestBoat) != item.hasChest)
+ {
+ Minecraft minecraft = Minecraft.getInstance();
+ boat = item.hasChest ? new ChestBoat(minecraft.level, 0, 0, 0) : new Boat(minecraft.level, 0, 0, 0);
+ boat.setVariant(item.type);
+ }
+
+ // If somehow the boat is still null, then we can't render anything.
+ return boat != null;
+ }
+
+ private boolean updateHorse(ItemStack horseArmorItem)
+ {
+ // If this isn't a horse armor item, we can't render anything.
+ if (!(horseArmorItem.getItem() instanceof HorseArmorItem))
+ {
+ return false;
+ }
+
+ if (horse == null)
+ {
+ Minecraft minecraft = Minecraft.getInstance();
+ horse = EntityType.HORSE.create(minecraft.level);
+ horse.setInvisible(true);
+ horse.canUpdate(false);
+ }
+
+ // If somehow the horse is still null, then we can't render anything.
+ if (horse == null)
+ {
+ return false;
+ }
+
+ // If the item has changed, then we need to update the horse.
+ if (cachedHorseArmorItem != Pair.of(horseArmorItem.getItem(), horseArmorItem.getTag()))
+ {
+ // Equip the horse with the armor.
+ horse.setItemSlot(EquipmentSlot.CHEST, horseArmorItem);
+
+ cachedHorseArmorItem = Pair.of(horseArmorItem.getItem(), horseArmorItem.getTag());
+ }
+ return true;
+ }
+
+ private ModelBounds boundsFromVertices(List<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(net.minecraftforge.registries.ForgeRegistries.ITEMS.getKey(stack.getItem())));
+ crashreportcategory.setDetail("Item Damage", () -> {
+ return String.valueOf(stack.getDamageValue());
+ });
+ crashreportcategory.setDetail("Item NBT", () -> {
+ return String.valueOf((Object)stack.getTag());
+ });
+ crashreportcategory.setDetail("Item Foil", () -> {
+ return String.valueOf(stack.hasFoil());
+ });
+ throw new ReportedException(crashreport);
+ }
+
+ blitOffset -= 50.0f;
+ }
+
public void renderItemModelIntoGUIWithAlpha(ItemStack stack, int x, int y, float alpha)
{
BakedModel bakedModel = mc.getItemRenderer().getModel(stack, null, null, 0);
@@ -65,9 +633,12 @@ public class CustomItemRenderer extends ItemRenderer
iconFrameBuffer.clear(Minecraft.ON_OSX);
iconFrameBuffer.bindWrite(true);
+ Matrix4f matrix = new Matrix4f();
+ matrix.setOrtho(0.0f, iconFrameBuffer.width, iconFrameBuffer.height, 0.0f, 1000.0f, 3000.0f);
+
RenderSystem.clear(GL11.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX);
RenderSystem.backupProjectionMatrix();
- RenderSystem.setProjectionMatrix(Matrix4f.orthographic(0.0f, iconFrameBuffer.width, iconFrameBuffer.height, 0.0f, 1000.0f, 3000.0f));
+ RenderSystem.setProjectionMatrix(matrix);
Lighting.setupFor3DItems();
@@ -75,23 +646,23 @@ public class CustomItemRenderer extends ItemRenderer
RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS);
RenderSystem.enableBlend();
RenderSystem.blendFuncSeparate(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ONE_MINUS_SRC_ALPHA);
- RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
+ RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
PoseStack modelViewStack = RenderSystem.getModelViewStack();
modelViewStack.pushPose();
modelViewStack.setIdentity();
modelViewStack.translate(48.0f, 48.0f, -2000.0f);
- modelViewStack.scale(96.0F, 96.0F, 96.0F);
+ modelViewStack.scale(96.0f, 96.0f, 96.0f);
RenderSystem.applyModelViewMatrix();
PoseStack poseStack = new PoseStack();
- MultiBufferSource.BufferSource multibuffersource$buffersource = Minecraft.getInstance().renderBuffers().bufferSource();
- boolean flag = !bakedModel.usesBlockLight();
- if (flag) { Lighting.setupForFlatItems(); }
+ BufferSource bufferSource = Minecraft.getInstance().renderBuffers().bufferSource();
+ boolean flatLighting = !bakedModel.usesBlockLight();
+ if (flatLighting) { Lighting.setupForFlatItems(); }
- render(stack, ItemTransforms.TransformType.GUI, false, poseStack, multibuffersource$buffersource, 0xF000F0, OverlayTexture.NO_OVERLAY, bakedModel);
- multibuffersource$buffersource.endBatch();
+ render(stack, ItemTransforms.TransformType.GUI, false, poseStack, bufferSource, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, bakedModel);
+ bufferSource.endBatch();
RenderSystem.enableDepthTest();
- if (flag) { Lighting.setupFor3DItems(); }
+ if (flatLighting) { Lighting.setupFor3DItems(); }
modelViewStack.popPose();
RenderSystem.applyModelViewMatrix();
diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java
new file mode 100644
index 0000000..7554893
--- /dev/null
+++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java
@@ -0,0 +1,79 @@
+package com.anthonyhilyard.iceberg.renderer;
+
+import java.util.List;
+
+import org.apache.commons.compress.utils.Lists;
+import org.joml.Vector3f;
+
+import com.mojang.blaze3d.vertex.VertexConsumer;
+
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.RenderType;
+
+public class VertexCollector implements MultiBufferSource
+{
+ private final List<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;
+ }
+}
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 73800cb..1421c37 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java
@@ -7,7 +7,7 @@ import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
-import com.mojang.math.Matrix4f;
+import org.joml.Matrix4f;
public class GuiHelper
{
diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java b/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java
index b21c168..df9e292 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java
@@ -9,7 +9,7 @@ import java.util.function.BiPredicate;
import java.util.List;
import net.minecraft.client.Minecraft;
-import net.minecraft.core.Registry;
+import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NumericTag;
import net.minecraft.nbt.Tag;
@@ -87,14 +87,17 @@ public class Selectors
public static List<SelectorDocumentation> 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")
);
}
@@ -105,6 +108,25 @@ public class Selectors
*/
public static boolean validateSelector(String value)
{
+ // First check if this is a combination of selectors.
+ if (value.contains("+"))
+ {
+ for (String selector : value.split("\\+"))
+ {
+ if (!validateSelector(selector))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // If this is a negation, remove the ~ and validate the rest.
+ if (value.startsWith("~"))
+ {
+ return validateSelector(value.substring(1));
+ }
+
// This is a tag, which should be a resource location.
if (value.startsWith("$"))
{
@@ -151,12 +173,45 @@ public class Selectors
@SuppressWarnings({"deprecation", "removal"})
public static boolean itemMatches(ItemStack item, String selector)
{
- String itemResourceLocation = ForgeRegistries.ITEMS.getKey(item.getItem()).toString();
+ // If this is a combination of selectors, check each one.
+ if (selector.contains("+"))
+ {
+ for (String subSelector : selector.split("\\+"))
+ {
+ if (!itemMatches(item, subSelector))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // If this is a negation, remove the ~ and check the rest.
+ if (selector.startsWith("~"))
+ {
+ return !itemMatches(item, selector.substring(1));
+ }
+
+ // Wildcard
+ if (selector.contentEquals("*"))
+ {
+ return true;
+ }
+
// Item ID
+ String itemResourceLocation = ForgeRegistries.ITEMS.getKey(item.getItem()).toString();
if (selector.equals(itemResourceLocation) || selector.equals(itemResourceLocation.replace("minecraft:", "")))
{
return true;
}
+ // Mod ID
+ else if (selector.startsWith("@"))
+ {
+ if (itemResourceLocation.startsWith(selector.substring(1) + ":"))
+ {
+ return true;
+ }
+ }
// Item name color
else if (selector.startsWith("#"))
{
@@ -174,18 +229,10 @@ public class Selectors
return true;
}
}
- // Mod ID
- else if (selector.startsWith("@"))
- {
- if (itemResourceLocation.startsWith(selector.substring(1) + ":"))
- {
- return true;
- }
- }
// Item tag
else if (selector.startsWith("$"))
{
- Optional<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;
diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java b/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java
index db952f8..47019f5 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java
@@ -10,14 +10,25 @@ import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import com.anthonyhilyard.iceberg.Loader;
import com.anthonyhilyard.iceberg.events.GatherComponentsExtEvent;
import com.anthonyhilyard.iceberg.events.RenderTooltipExtEvent;
+
+import com.mojang.blaze3d.systems.RenderSystem;
+import com.mojang.blaze3d.vertex.BufferBuilder;
+import com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.DefaultVertexFormat;
+import com.mojang.blaze3d.vertex.Tesselator;
+import com.mojang.blaze3d.vertex.VertexFormat;
+import com.mojang.datafixers.util.Either;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTextTooltip;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
+import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil;
+import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.client.renderer.MultiBufferSource.BufferSource;
@@ -26,23 +37,52 @@ import net.minecraft.locale.Language;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.network.chat.Style;
+import net.minecraft.network.chat.TextColor;
import net.minecraft.util.FormattedCharSequence;
-import com.mojang.blaze3d.vertex.Tesselator;
-import com.mojang.datafixers.util.Either;
-
import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraft.world.item.ItemStack;
-import com.mojang.math.Matrix4f;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.event.RenderTooltipEvent;
+import net.minecraftforge.client.event.RegisterClientTooltipComponentFactoriesEvent;
import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.joml.Matrix4f;
public class Tooltips
{
+ public record TooltipColors(TextColor backgroundColorStart, TextColor backgroundColorEnd, TextColor borderColorStart, TextColor borderColorEnd) {}
+ private static final TooltipColors DEFAULT_COLORS = new TooltipColors(TextColor.fromRgb(0xF0100010), TextColor.fromRgb(0xF0100010), TextColor.fromRgb(0x505000FF), TextColor.fromRgb(0x5028007F));
+
private static final FormattedCharSequence SPACE = FormattedCharSequence.forward(" ", Style.EMPTY);
private static ItemRenderer itemRenderer = null;
+ private static boolean tooltipWidthWarningShown = false;
+
+ public static TooltipColors currentColors = DEFAULT_COLORS;
+
+ public static class TitleBreakComponent implements TooltipComponent, ClientTooltipComponent
+ {
+ @Override
+ public int getHeight() { return 0; }
+
+ @Override
+ public int getWidth(Font font) { return 0; }
+
+ public static void registerFactory()
+ {
+ FMLJavaModLoadingContext.get().getModEventBus().addListener(TitleBreakComponent::onRegisterTooltipEvent);
+ }
+
+ private static void onRegisterTooltipEvent(RegisterClientTooltipComponentFactoriesEvent event)
+ {
+ event.register(TitleBreakComponent.class, x -> x);
+ }
+ }
+
+ public static interface InlineComponent { }
public static class TooltipInfo
{
@@ -51,10 +91,12 @@ public class Tooltips
private Font font;
private List<ClientTooltipComponent> components = new ArrayList<>();
- public TooltipInfo(List<ClientTooltipComponent> components, Font font)
+ 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; }
@@ -85,6 +127,39 @@ public class Tooltips
}
}
+ public static int calculateTitleLines(List<ClientTooltipComponent> components)
+ {
+ if (components == null || components.isEmpty())
+ {
+ return 0;
+ }
+
+ // Determine the number of "title lines". This will be the number of text components before the first TitleBreakComponent.
+ // If for some reason there is no TitleBreakComponent, we'll default to 1.
+ int titleLines = 0;
+ boolean foundTitleBreak = false;
+ for (ClientTooltipComponent component : components)
+ {
+ if (component instanceof ClientTextTooltip)
+ {
+ titleLines++;
+ }
+ else if (component instanceof TitleBreakComponent)
+ {
+ foundTitleBreak = true;
+ break;
+ }
+ }
+
+ // We didn't find a title break (shouldn't happen normally), so default to 1.
+ if (!foundTitleBreak)
+ {
+ titleLines = 1;
+ }
+
+ return titleLines;
+ }
+
public static void renderItemTooltip(@Nonnull final ItemStack stack, PoseStack poseStack, TooltipInfo info,
Rect2i rect, int screenWidth, int screenHeight,
int backgroundColor, int borderColorStart, int borderColorEnd)
@@ -125,7 +200,7 @@ public class Tooltips
// Center the title now if needed.
if (centeredTitle)
{
- info = new TooltipInfo(centerTitle(info.getComponents(), info.getFont(), rect.getWidth()), info.getFont());
+ info = new TooltipInfo(centerTitle(info.getComponents(), info.getFont(), info.getMaxLineWidth(), info.getTitleLines()), info.getFont(), info.getTitleLines());
}
int rectX = rect.getX() + 4;
@@ -148,7 +223,12 @@ public class Tooltips
final int zLevel = 400;
float f = itemRenderer.blitOffset;
itemRenderer.blitOffset = zLevel;
- Matrix4f mat = poseStack.last().pose();
+
+ Tesselator tesselator = Tesselator.getInstance();
+ BufferBuilder bufferbuilder = tesselator.getBuilder();
+ RenderSystem.setShader(GameRenderer::getPositionColorShader);
+ bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
+ Matrix4f matrix4f = poseStack.last().pose();
RenderTooltipExtEvent.Color colorEvent = new RenderTooltipExtEvent.Color(stack, poseStack, rectX, rectY, info.getFont(), backgroundColorStart, backgroundColorEnd, borderColorStart, borderColorEnd, info.getComponents(), comparison, index);
MinecraftForge.EVENT_BUS.post(colorEvent);
@@ -158,35 +238,41 @@ public class Tooltips
borderColorStart = colorEvent.getBorderStart();
borderColorEnd = colorEvent.getBorderEnd();
- GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY - 4, rectX + rect.getWidth() + 3, rectY - 3, backgroundColorStart, backgroundColorStart);
- GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY + rect.getHeight() + 3, rectX + rect.getWidth() + 3, rectY + rect.getHeight() + 4, backgroundColorEnd, backgroundColorEnd);
- GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY - 3, rectX + rect.getWidth() + 3, rectY + rect.getHeight() + 3, backgroundColorStart, backgroundColorEnd);
- GuiHelper.drawGradientRect(mat, zLevel, rectX - 4, rectY - 3, rectX - 3, rectY + rect.getHeight() + 3, backgroundColorStart, backgroundColorEnd);
- GuiHelper.drawGradientRect(mat, zLevel, rectX + rect.getWidth() + 3, rectY - 3, rectX + rect.getWidth() + 4, rectY + rect.getHeight() + 3, backgroundColorStart, backgroundColorEnd);
- GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY - 3 + 1, rectX - 3 + 1, rectY + rect.getHeight() + 3 - 1, borderColorStart, borderColorEnd);
- GuiHelper.drawGradientRect(mat, zLevel, rectX + rect.getWidth() + 2, rectY - 3 + 1, rectX + rect.getWidth() + 3, rectY + rect.getHeight() + 3 - 1, borderColorStart, borderColorEnd);
- GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY - 3, rectX + rect.getWidth() + 3, rectY - 3 + 1, borderColorStart, borderColorStart);
- GuiHelper.drawGradientRect(mat, zLevel, rectX - 3, rectY + rect.getHeight() + 2, rectX + rect.getWidth() + 3, rectY + rect.getHeight() + 3, borderColorEnd, borderColorEnd);
+ currentColors = new TooltipColors(TextColor.fromRgb(backgroundColorStart), TextColor.fromRgb(backgroundColorEnd), TextColor.fromRgb(borderColorStart), TextColor.fromRgb(borderColorEnd));
+
+ TooltipRenderUtil.renderTooltipBackground((matrix, bufferBuilder, left, top, right, bottom, z, startColor, endColor) -> {
+ GuiHelper.drawGradientRect(matrix, bufferBuilder, left, top, right, bottom, z, startColor, endColor);
+ }, matrix4f, bufferbuilder, rectX, rectY, rect.getWidth(), rect.getHeight(), zLevel);
- BufferSource renderType = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
- poseStack.translate(0.0D, 0.0D, zLevel);
+ RenderSystem.enableDepthTest();
+ RenderSystem.disableTexture();
+ RenderSystem.enableBlend();
+ RenderSystem.defaultBlendFunc();
+ BufferUploader.drawWithShader(bufferbuilder.end());
+ RenderSystem.disableBlend();
+ RenderSystem.enableTexture();
+ BufferSource bufferSource = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
+ poseStack.translate(0.0f, 0.0f, zLevel);
int tooltipTop = rectY;
- boolean titleRendered = false;
+ int titleLines = info.getTitleLines();
for (int componentNumber = 0; componentNumber < info.getComponents().size(); ++componentNumber)
{
ClientTooltipComponent textComponent = info.getComponents().get(componentNumber);
- textComponent.renderText(preEvent.getFont(), rectX, tooltipTop, mat, renderType);
+ textComponent.renderText(preEvent.getFont(), rectX, tooltipTop, matrix4f, bufferSource);
tooltipTop += textComponent.getHeight();
- if (!titleRendered && textComponent instanceof ClientTextTooltip)
+ if ((textComponent instanceof ClientTextTooltip || textComponent instanceof InlineComponent) && titleLines > 0)
{
- tooltipTop += 2;
- titleRendered = true;
+ titleLines -= (textComponent instanceof InlineComponent) ? 2 : 1;
+ if (titleLines <= 0)
+ {
+ tooltipTop += 2;
+ }
}
}
- renderType.endBatch();
+ bufferSource.endBatch();
poseStack.popPose();
tooltipTop = rectY;
@@ -226,7 +312,22 @@ public class Tooltips
}
int tooltipTextWidth = event.getTooltipElements().stream()
- .mapToInt(either -> either.map(font::width, component -> 0))
+ .mapToInt(either -> either.map(component -> {
+ try
+ {
+ return font.width(component);
+ }
+ catch (Exception e)
+ {
+ // Log this exception, but only once.
+ if (!tooltipWidthWarningShown)
+ {
+ Loader.LOGGER.error("Error rendering tooltip component: \n" + ExceptionUtils.getStackTrace(e));
+ tooltipWidthWarningShown = true;
+ }
+ return 0;
+ }
+ }, component -> 0))
.max()
.orElse(0);
@@ -239,9 +340,13 @@ public class Tooltips
if (tooltipX < 4) // if the tooltip doesn't fit on the screen
{
if (mouseX > screenWidth / 2)
+ {
tooltipTextWidth = mouseX - 12 - 8;
+ }
else
+ {
tooltipTextWidth = screenWidth - 16 - mouseX;
+ }
needsWrap = true;
}
}
@@ -300,12 +405,24 @@ public class Tooltips
int tooltipTextWidth = minWidth;
int tooltipHeight = components.size() == 1 ? -2 : 0;
+ int titleLines = calculateTitleLines(components);
if (centeredTitle)
{
- components = centerTitle(components, font, minWidth);
+ // Calculate the current tooltip width prior to centering.
+ for (ClientTooltipComponent component : components)
+ {
+ int componentWidth = component.getWidth(event.getFont());
+ if (componentWidth > tooltipTextWidth)
+ {
+ tooltipTextWidth = componentWidth;
+ }
+ }
+ components = centerTitle(components, font, tooltipTextWidth, titleLines);
}
+ tooltipTextWidth = minWidth;
+
for (ClientTooltipComponent component : components)
{
int componentWidth = component.getWidth(event.getFont());
@@ -333,25 +450,20 @@ public class Tooltips
return rect;
}
- public static List<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, 1);
+ }
- for (ClientTooltipComponent clienttooltipcomponent : 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())
{
- if (clienttooltipcomponent == null)
- {
- return components;
- }
- int componentWidth = clienttooltipcomponent.getWidth(font);
- if (componentWidth > tooltipWidth)
- {
- tooltipWidth = componentWidth;
- }
+ return result;
}
- // TODO: If the title is multiple lines, we need to extend this for each one.
// Find the title component, which is the first text component.
int titleIndex = 0;
for (ClientTooltipComponent clienttooltipcomponent : components)
@@ -363,29 +475,30 @@ public class Tooltips
titleIndex++;
}
- if (titleIndex >= components.size())
+ for (int i = 0; i < titleLines; i++)
{
- titleIndex = 0;
- }
+ ClientTooltipComponent titleComponent = components.get(titleIndex + i);
- List<FormattedText> recomposedLines = StringRecomposer.recompose(List.of(components.get(titleIndex)));
- if (recomposedLines.isEmpty())
- {
- return components;
- }
+ if (titleComponent != null)
+ {
+ List<FormattedText> recomposedLines = StringRecomposer.recompose(List.of(titleComponent));
+ if (recomposedLines.isEmpty())
+ {
+ return components;
+ }
- List<ClientTooltipComponent> result = new ArrayList<>(components);
+ FormattedCharSequence title = Language.getInstance().getVisualOrder(recomposedLines.get(0));
- FormattedCharSequence title = Language.getInstance().getVisualOrder(recomposedLines.get(0));
- while (result.get(titleIndex).getWidth(font) < tooltipWidth)
- {
- title = FormattedCharSequence.fromList(List.of(SPACE, title, SPACE));
- if (title == null)
- {
- break;
+ while (ClientTooltipComponent.create(title).getWidth(font) < width)
+ {
+ title = FormattedCharSequence.fromList(List.of(SPACE, title, SPACE));
+ if (title == null)
+ {
+ break;
+ }
+ }
+ result.set(titleIndex + i, ClientTooltipComponent.create(title));
}
-
- result.set(titleIndex, ClientTooltipComponent.create(title));
}
return result;
}
diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg
index a58652d..04c2f3f 100644
--- a/src/main/resources/META-INF/accesstransformer.cfg
+++ b/src/main/resources/META-INF/accesstransformer.cfg
@@ -1,2 +1,8 @@
public net.minecraft.client.gui.screens.inventory.tooltip.ClientTextTooltip f_169936_ # text
-protected net.minecraftforge.common.ForgeConfigSpec <init>(Lcom/electronwill/nightconfig/core/UnmodifiableConfig;Lcom/electronwill/nightconfig/core/UnmodifiableConfig;Ljava/util/Map;)V # constructor \ No newline at end of file
+protected net.minecraftforge.common.ForgeConfigSpec <init>(Lcom/electronwill/nightconfig/core/UnmodifiableConfig;Lcom/electronwill/nightconfig/core/UnmodifiableConfig;Ljava/util/Map;)V # constructor
+public net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil f_262744_ # BACKGROUND_COLOR
+public net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil f_262725_ # BORDER_COLOR_TOP
+public net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil f_262743_ # BORDER_COLOR_BOTTOM
+public net.minecraft.world.item.MinecartItem f_42935_ # type
+public net.minecraft.world.item.BoatItem f_40616_ # type
+public net.minecraft.world.item.BoatItem f_220011_ # hasChest
diff --git a/src/main/resources/iceberg.mixins.json b/src/main/resources/iceberg.mixins.json
index 8acd6e0..c272c92 100644
--- a/src/main/resources/iceberg.mixins.json
+++ b/src/main/resources/iceberg.mixins.json
@@ -11,7 +11,8 @@
"ScreenMixin",
"ClientPacketListenerMixin",
"TextColorMixin",
- "MinecraftMixin"
+ "MinecraftMixin",
+ "TooltipRenderUtilMixin"
],
"server": [
"MainMixin"