From d2d41ee7e950c311c8de1301f79290fa0719775a Mon Sep 17 00:00:00 2001 From: Constructor Date: Tue, 9 Dec 2025 04:40:54 +0100 Subject: [PATCH 1/3] Added Container Preview --- .../lambda/mixin/items/BlockItemMixin.java | 42 +++ .../lambda/mixin/render/DrawContextMixin.java | 60 ++++ .../com/lambda/mixin/render/ScreenMixin.java | 10 + .../mixin/render/TooltipComponentMixin.java | 8 + .../module/modules/render/ContainerPreview.kt | 321 ++++++++++++++++++ src/main/resources/lambda.mixins.common.json | 2 + 6 files changed, 443 insertions(+) create mode 100644 src/main/java/com/lambda/mixin/items/BlockItemMixin.java create mode 100644 src/main/java/com/lambda/mixin/render/DrawContextMixin.java create mode 100644 src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt diff --git a/src/main/java/com/lambda/mixin/items/BlockItemMixin.java b/src/main/java/com/lambda/mixin/items/BlockItemMixin.java new file mode 100644 index 000000000..27a3f87cd --- /dev/null +++ b/src/main/java/com/lambda/mixin/items/BlockItemMixin.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.mixin.items; + +import com.lambda.module.modules.render.ContainerPreview; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.tooltip.TooltipData; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.Optional; + +@Mixin(BlockItem.class) +public class BlockItemMixin extends Item { + public BlockItemMixin(Settings settings) { + super(settings); + } + + @Override + public Optional getTooltipData(ItemStack stack) { + if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isShulkerBox(stack)) { + return Optional.of(new ContainerPreview.ShulkerComponent(stack)); + } + return super.getTooltipData(stack); + } +} diff --git a/src/main/java/com/lambda/mixin/render/DrawContextMixin.java b/src/main/java/com/lambda/mixin/render/DrawContextMixin.java new file mode 100644 index 000000000..b2aed2270 --- /dev/null +++ b/src/main/java/com/lambda/mixin/render/DrawContextMixin.java @@ -0,0 +1,60 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.mixin.render; + +import com.lambda.module.modules.render.ContainerPreview; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.tooltip.TooltipComponent; +import net.minecraft.client.gui.tooltip.TooltipPositioner; +import net.minecraft.item.ItemStack; +import net.minecraft.item.tooltip.TooltipData; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; +import java.util.Optional; + +@Mixin(DrawContext.class) +public class DrawContextMixin { + @Inject(method = "drawTooltip(Lnet/minecraft/client/font/TextRenderer;Ljava/util/List;Ljava/util/Optional;IILnet/minecraft/util/Identifier;)V", at = @At("HEAD"), cancellable = true) + private void onDrawTooltip(TextRenderer textRenderer, List text, Optional data, int x, int y, Identifier texture, CallbackInfo ci) { + if (!ContainerPreview.INSTANCE.isEnabled()) return; + + // Don't intercept if we're rendering a sub-tooltip (prevents infinite recursion) + if (ContainerPreview.isRenderingSubTooltip()) return; + + // If we're locked, always render our locked tooltip and cancel any other tooltip + if (ContainerPreview.isLocked()) { + ci.cancel(); + ContainerPreview.renderLockedTooltip((DrawContext)(Object)this, textRenderer); + return; + } + + // Check if this is a shulker box tooltip with ContainerPreview + if (data.isPresent() && data.get() instanceof ContainerPreview.ShulkerComponent component) { + // Cancel the default tooltip and render our custom one + ci.cancel(); + ContainerPreview.renderShulkerTooltip((DrawContext)(Object)this, textRenderer, component, x, y); + } + } +} diff --git a/src/main/java/com/lambda/mixin/render/ScreenMixin.java b/src/main/java/com/lambda/mixin/render/ScreenMixin.java index 1780bb8a7..b27403a3e 100644 --- a/src/main/java/com/lambda/mixin/render/ScreenMixin.java +++ b/src/main/java/com/lambda/mixin/render/ScreenMixin.java @@ -18,7 +18,9 @@ package com.lambda.mixin.render; import com.lambda.gui.components.QuickSearch; +import com.lambda.module.modules.render.ContainerPreview; import com.lambda.module.modules.render.NoRender; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import org.lwjgl.glfw.GLFW; @@ -42,4 +44,12 @@ private void onKeyPressed(int keyCode, int scanCode, int modifiers, CallbackInfo private void injectRenderInGameBackground(DrawContext context, CallbackInfo ci) { if (NoRender.INSTANCE.isEnabled() && NoRender.getNoGuiShadow()) ci.cancel(); } + + @Inject(method = "renderWithTooltip", at = @At("TAIL")) + private void onRenderWithTooltip(DrawContext context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) { + // Render locked container preview tooltip at the end of screen rendering + if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { + ContainerPreview.renderLockedTooltip(context, MinecraftClient.getInstance().textRenderer); + } + } } diff --git a/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java b/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java index b33fba5c1..7b08bb521 100644 --- a/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java +++ b/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java @@ -17,6 +17,7 @@ package com.lambda.mixin.render; +import com.lambda.module.modules.render.ContainerPreview; import com.lambda.module.modules.render.MapPreview; import net.minecraft.client.gui.tooltip.BundleTooltipComponent; import net.minecraft.client.gui.tooltip.ProfilesTooltipComponent; @@ -32,6 +33,13 @@ public interface TooltipComponentMixin { @Inject(method = "of(Lnet/minecraft/item/tooltip/TooltipData;)Lnet/minecraft/client/gui/tooltip/TooltipComponent;", at = @At("HEAD"), cancellable = true) private static void of(TooltipData tooltipData, CallbackInfoReturnable cir) { + // Handle ContainerPreview shulker box tooltip + if (ContainerPreview.INSTANCE.isEnabled() && tooltipData instanceof ContainerPreview.ShulkerComponent shulkerComponent) { + cir.setReturnValue(shulkerComponent); + return; + } + + // Handle MapPreview map tooltip if (MapPreview.INSTANCE.isEnabled()) cir.setReturnValue((switch (tooltipData) { case MapPreview.MapComponent mapComponent -> mapComponent; case BundleTooltipData bundleTooltipData -> new BundleTooltipComponent(bundleTooltipData.contents()); diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt new file mode 100644 index 000000000..25205ed68 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -0,0 +1,321 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.render + +import com.lambda.Lambda.mc +import com.lambda.config.settings.complex.Bind +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.KeyCode +import com.lambda.util.item.ItemStackUtils.shulkerBoxContents +import com.lambda.util.item.ItemUtils.shulkerBoxes +import net.minecraft.block.ShulkerBoxBlock +import net.minecraft.client.font.TextRenderer +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.tooltip.TooltipComponent +import net.minecraft.client.render.RenderLayer +import net.minecraft.item.BlockItem +import net.minecraft.item.ItemStack +import net.minecraft.item.tooltip.TooltipData +import net.minecraft.util.DyeColor +import net.minecraft.util.Identifier +import org.lwjgl.glfw.GLFW + +object ContainerPreview : Module( + name = "ContainerPreview", + description = "Renders shulker box contents visually in tooltips", + tag = ModuleTag.RENDER, +) { + private val lockKey by setting("Lock Key", Bind(KeyCode.LeftShift.code, 0, -1), "Key to lock the tooltip in place for item interaction") + private val useShift by setting("Use Shift", true, "Use shift key to lock tooltip (overrides lock key)") + private val colorTint by setting("Color Tint", true, "Tint the background with the shulker box color") + + private val background = Identifier.ofVanilla("textures/gui/container/shulker_box.png") + + // Locked tooltip state - store the stack instead of component for persistence + private var lockedStack: ItemStack? = null + private var lockedX: Int = 0 + private var lockedY: Int = 0 + + // Flag to prevent recursive tooltip rendering + @JvmStatic + var isRenderingSubTooltip: Boolean = false + private set + + // Tooltip dimensions (3 rows x 9 columns like shulker box) + private const val ROWS = 3 + private const val COLS = 9 + private const val SLOT_SIZE = 18 + private const val PADDING = 7 + private const val TITLE_HEIGHT = 14 + + @JvmStatic + val isLocked: Boolean + get() = lockedStack != null + + @JvmStatic + fun isLockKeyPressed(): Boolean { + if (!isEnabled) return false + if (useShift) return Screen.hasShiftDown() + val handle = mc.window.handle + return GLFW.glfwGetKey(handle, lockKey.key) == GLFW.GLFW_PRESS + } + + private fun getTooltipWidth(): Int = PADDING + COLS * SLOT_SIZE + PADDING + private fun getTooltipHeight(): Int = TITLE_HEIGHT + ROWS * SLOT_SIZE + PADDING + + /** + * Calculate text color based on background luminance. + * Returns dark text for light backgrounds and white text for dark backgrounds. + */ + private fun getTextColor(tintColor: Int): Int { + val r = ((tintColor shr 16) and 0xFF) / 255f + val g = ((tintColor shr 8) and 0xFF) / 255f + val b = (tintColor and 0xFF) / 255f + + // Calculate relative luminance using standard formula + val luminance = 0.299f * r + 0.587f * g + 0.114f * b + + // Use dark text on light backgrounds, white text on dark backgrounds + return if (luminance > 0.5f) 0x404040 else 0xFFFFFF + } + + @JvmStatic + fun renderShulkerTooltip(context: DrawContext, textRenderer: TextRenderer, component: ShulkerComponent, mouseX: Int, mouseY: Int) { + // Calculate tooltip position + val width = getTooltipWidth() + val height = getTooltipHeight() + + // Handle locking - lock when key is pressed while hovering a shulker + val lockKeyPressed = isLockKeyPressed() + + if (lockKeyPressed && lockedStack == null) { + // Lock when key is pressed while hovering + lockedStack = component.stack.copy() // Copy the stack to preserve it + lockedX = calculateTooltipX(mouseX, width) + lockedY = calculateTooltipY(mouseY, height) + } else if (!lockKeyPressed && lockedStack != null) { + // Unlock when key is released + lockedStack = null + } + + // If locked, delegate to renderLockedTooltip + if (isLocked) { + renderLockedTooltipInternal(context, textRenderer) + return + } + + // Not locked - render normal tooltip following mouse + renderTooltipForStack(context, textRenderer, component.stack, calculateTooltipX(mouseX, width), calculateTooltipY(mouseY, height), false) + } + + /** + * Render the locked tooltip - called from mixin when we're locked + */ + @JvmStatic + fun renderLockedTooltip(context: DrawContext, textRenderer: TextRenderer) { + // Update lock state - unlock if key released + if (!isLockKeyPressed()) { + lockedStack = null + return + } + renderLockedTooltipInternal(context, textRenderer) + } + + private fun renderLockedTooltipInternal(context: DrawContext, textRenderer: TextRenderer) { + val stack = lockedStack ?: return + renderTooltipForStack(context, textRenderer, stack, lockedX, lockedY, true) + } + + private fun renderTooltipForStack(context: DrawContext, textRenderer: TextRenderer, stack: ItemStack, x: Int, y: Int, allowHover: Boolean) { + val contents = stack.shulkerBoxContents + val width = getTooltipWidth() + val height = getTooltipHeight() + + val matrices = context.matrices + matrices.push() + matrices.translate(0f, 0f, 400f) + + // Get shulker box color + val color = getShulkerColor(stack) + val tintColor = if (colorTint && color != null) { + color.entityColor + } else { + 0xFFFFFFFF.toInt() + } + + // Draw background with color tint + drawBackground(context, x, y, width, height, tintColor) + + // Draw title (shulker box name) with appropriate text color + val name = stack.name + val textColor = getTextColor(tintColor) + context.drawText(textRenderer, name, x + PADDING, y + 4, textColor, false) + + // Draw items + val startX = x + PADDING + 1 + val startY = y + TITLE_HEIGHT + + // Get actual mouse position for hover detection + val actualMouseX = (mc.mouse.x * mc.window.scaledWidth / mc.window.width).toInt() + val actualMouseY = (mc.mouse.y * mc.window.scaledHeight / mc.window.height).toInt() + + var hoveredStack: ItemStack? = null + var hoveredSlotX = 0 + var hoveredSlotY = 0 + + for ((index, item) in contents.withIndex()) { + if (index >= COLS * ROWS) break + + val slotX = index % COLS + val slotY = index / COLS + + val itemX = startX + slotX * SLOT_SIZE + val itemY = startY + slotY * SLOT_SIZE + + // Check if this slot is hovered (only when locked/allowHover) + if (allowHover) { + val isHovered = actualMouseX >= itemX && actualMouseX < itemX + 16 && + actualMouseY >= itemY && actualMouseY < itemY + 16 + + if (isHovered && !item.isEmpty) { + // Draw highlight + context.fill(itemX, itemY, itemX + 16, itemY + 16, 0x80FFFFFF.toInt()) + hoveredStack = item + hoveredSlotX = actualMouseX + hoveredSlotY = actualMouseY + } + } + + if (!item.isEmpty) { + context.drawItem(item, itemX, itemY) + context.drawStackOverlay(textRenderer, item, itemX, itemY) + } + } + + matrices.pop() + + // Draw sub-tooltip for hovered item at higher Z level + if (hoveredStack != null && allowHover) { + matrices.push() + matrices.translate(0f, 0f, 500f) // Higher Z than the main tooltip + // Set flag to prevent recursive mixin interception + isRenderingSubTooltip = true + try { + context.drawItemTooltip(textRenderer, hoveredStack, hoveredSlotX, hoveredSlotY) + } finally { + isRenderingSubTooltip = false + } + matrices.pop() + } + } + + private fun calculateTooltipX(mouseX: Int, width: Int): Int { + val screenWidth = mc.window.scaledWidth + var x = mouseX + 12 + if (x + width > screenWidth) { + x = mouseX - width - 12 + } + if (x < 0) x = 0 + return x + } + + private fun calculateTooltipY(mouseY: Int, height: Int): Int { + val screenHeight = mc.window.scaledHeight + var y = mouseY - 12 + if (y + height > screenHeight) { + y = screenHeight - height + } + if (y < 0) y = 0 + return y + } + + private fun drawBackground(context: DrawContext, x: Int, y: Int, width: Int, height: Int, tintColor: Int) { + // Extract RGB components for tinting + val r = ((tintColor shr 16) and 0xFF) / 255f + val g = ((tintColor shr 8) and 0xFF) / 255f + val b = (tintColor and 0xFF) / 255f + + // Draw the shulker box texture background with tint + // The shulker_box.png texture is 176x166 + // Top part (title area): y=0 to y=17 + // Slot area: y=17 to y=89 (3 rows of 18px each + borders) + // Bottom: y=160 onwards + + context.drawTexture( + RenderLayer::getGuiTextured, + background, + x, y, + 0f, 0f, + width, TITLE_HEIGHT, + 256, 256, + tintColor + ) + + // Middle rows + for (row in 0 until ROWS) { + context.drawTexture( + RenderLayer::getGuiTextured, + background, + x, y + TITLE_HEIGHT + row * SLOT_SIZE, + 0f, 17f, + width, SLOT_SIZE, + 256, 256, + tintColor + ) + } + + // Bottom + context.drawTexture( + RenderLayer::getGuiTextured, + background, + x, y + TITLE_HEIGHT + ROWS * SLOT_SIZE, + 0f, 160f, + width, PADDING, + 256, 256, + tintColor + ) + } + + private fun getShulkerColor(stack: ItemStack): DyeColor? { + val item = stack.item + if (item is BlockItem) { + val block = item.block + if (block is ShulkerBoxBlock) { + return block.color + } + } + return null + } + + @JvmStatic + fun isShulkerBox(stack: ItemStack): Boolean { + return stack.item in shulkerBoxes + } + + class ShulkerComponent(val stack: ItemStack) : TooltipData, TooltipComponent { + val contents: List + get() = stack.shulkerBoxContents + + // These methods are not used since we render the tooltip ourselves + override fun drawItems(textRenderer: TextRenderer, x: Int, y: Int, width: Int, height: Int, context: DrawContext) {} + override fun getHeight(textRenderer: TextRenderer): Int = 0 + override fun getWidth(textRenderer: TextRenderer): Int = 0 + } +} diff --git a/src/main/resources/lambda.mixins.common.json b/src/main/resources/lambda.mixins.common.json index c865de961..758e2ae26 100644 --- a/src/main/resources/lambda.mixins.common.json +++ b/src/main/resources/lambda.mixins.common.json @@ -21,6 +21,7 @@ "input.KeyboardMixin", "input.MouseMixin", "items.BarrierBlockMixin", + "items.BlockItemMixin", "items.FilledMapItemMixin", "network.ClientConnectionMixin", "network.ClientLoginNetworkMixin", @@ -46,6 +47,7 @@ "render.ChunkOcclusionDataBuilderMixin", "render.DebugHudMixin", "render.DebugRendererMixin", + "render.DrawContextMixin", "render.ElytraFeatureRendererMixin", "render.EnchantingTableBlockEntityRendererMixin", "render.EntityRendererMixin", From fcb458effcfe30d85e16f7574c4318fe6adb17ca Mon Sep 17 00:00:00 2001 From: Constructor Date: Tue, 9 Dec 2025 05:00:32 +0100 Subject: [PATCH 2/3] Ender Chest support and fixes --- .../lambda/mixin/items/BlockItemMixin.java | 4 +- .../lambda/mixin/render/DrawContextMixin.java | 4 +- .../mixin/render/HandledScreenMixin.java | 48 +++++++ .../com/lambda/mixin/render/ScreenMixin.java | 10 +- .../mixin/render/TooltipComponentMixin.java | 6 +- .../module/modules/render/ContainerPreview.kt | 124 ++++++++++++++---- src/main/resources/lambda.mixins.common.json | 1 + 7 files changed, 159 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/lambda/mixin/render/HandledScreenMixin.java diff --git a/src/main/java/com/lambda/mixin/items/BlockItemMixin.java b/src/main/java/com/lambda/mixin/items/BlockItemMixin.java index 27a3f87cd..c599cea55 100644 --- a/src/main/java/com/lambda/mixin/items/BlockItemMixin.java +++ b/src/main/java/com/lambda/mixin/items/BlockItemMixin.java @@ -34,8 +34,8 @@ public BlockItemMixin(Settings settings) { @Override public Optional getTooltipData(ItemStack stack) { - if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isShulkerBox(stack)) { - return Optional.of(new ContainerPreview.ShulkerComponent(stack)); + if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isPreviewableContainer(stack)) { + return Optional.of(new ContainerPreview.ContainerComponent(stack)); } return super.getTooltipData(stack); } diff --git a/src/main/java/com/lambda/mixin/render/DrawContextMixin.java b/src/main/java/com/lambda/mixin/render/DrawContextMixin.java index b2aed2270..31d7b9589 100644 --- a/src/main/java/com/lambda/mixin/render/DrawContextMixin.java +++ b/src/main/java/com/lambda/mixin/render/DrawContextMixin.java @@ -50,8 +50,8 @@ private void onDrawTooltip(TextRenderer textRenderer, List text, Optional< return; } - // Check if this is a shulker box tooltip with ContainerPreview - if (data.isPresent() && data.get() instanceof ContainerPreview.ShulkerComponent component) { + // Check if this is a container tooltip with ContainerPreview + if (data.isPresent() && data.get() instanceof ContainerPreview.ContainerComponent component) { // Cancel the default tooltip and render our custom one ci.cancel(); ContainerPreview.renderShulkerTooltip((DrawContext)(Object)this, textRenderer, component, x, y); diff --git a/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java b/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java new file mode 100644 index 000000000..7057da433 --- /dev/null +++ b/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java @@ -0,0 +1,48 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.mixin.render; + +import com.lambda.module.modules.render.ContainerPreview; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(HandledScreen.class) +public class HandledScreenMixin { + @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) + private void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { + // Block clicks when container preview tooltip is locked and mouse is over it + if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { + if (ContainerPreview.isMouseOverLockedTooltip((int) mouseX, (int) mouseY)) { + cir.setReturnValue(true); + } + } + } + + @Inject(method = "mouseReleased", at = @At("HEAD"), cancellable = true) + private void onMouseReleased(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { + // Block releases when container preview tooltip is locked and mouse is over it + if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { + if (ContainerPreview.isMouseOverLockedTooltip((int) mouseX, (int) mouseY)) { + cir.setReturnValue(true); + } + } + } +} diff --git a/src/main/java/com/lambda/mixin/render/ScreenMixin.java b/src/main/java/com/lambda/mixin/render/ScreenMixin.java index b27403a3e..8fb28729e 100644 --- a/src/main/java/com/lambda/mixin/render/ScreenMixin.java +++ b/src/main/java/com/lambda/mixin/render/ScreenMixin.java @@ -20,6 +20,8 @@ import com.lambda.gui.components.QuickSearch; import com.lambda.module.modules.render.ContainerPreview; import com.lambda.module.modules.render.NoRender; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; @@ -45,9 +47,11 @@ private void injectRenderInGameBackground(DrawContext context, CallbackInfo ci) if (NoRender.INSTANCE.isEnabled() && NoRender.getNoGuiShadow()) ci.cancel(); } - @Inject(method = "renderWithTooltip", at = @At("TAIL")) - private void onRenderWithTooltip(DrawContext context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) { - // Render locked container preview tooltip at the end of screen rendering + @WrapOperation(method = "renderWithTooltip", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;render(Lnet/minecraft/client/gui/DrawContext;IIF)V")) + private void wrapRender(Screen instance, DrawContext context, int mouseX, int mouseY, float deltaTicks, Operation original) { + original.call(instance, context, mouseX, mouseY, deltaTicks); + + // Render locked container preview tooltip after screen rendering if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { ContainerPreview.renderLockedTooltip(context, MinecraftClient.getInstance().textRenderer); } diff --git a/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java b/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java index 7b08bb521..3b3d439ef 100644 --- a/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java +++ b/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java @@ -33,9 +33,9 @@ public interface TooltipComponentMixin { @Inject(method = "of(Lnet/minecraft/item/tooltip/TooltipData;)Lnet/minecraft/client/gui/tooltip/TooltipComponent;", at = @At("HEAD"), cancellable = true) private static void of(TooltipData tooltipData, CallbackInfoReturnable cir) { - // Handle ContainerPreview shulker box tooltip - if (ContainerPreview.INSTANCE.isEnabled() && tooltipData instanceof ContainerPreview.ShulkerComponent shulkerComponent) { - cir.setReturnValue(shulkerComponent); + // Handle ContainerPreview container tooltip (shulker boxes and ender chests) + if (ContainerPreview.INSTANCE.isEnabled() && tooltipData instanceof ContainerPreview.ContainerComponent containerComponent) { + cir.setReturnValue(containerComponent); return; } diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt index 25205ed68..2e3daeed1 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -19,6 +19,7 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc import com.lambda.config.settings.complex.Bind +import com.lambda.interaction.material.container.containers.EnderChestContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.KeyCode @@ -32,6 +33,7 @@ import net.minecraft.client.gui.tooltip.TooltipComponent import net.minecraft.client.render.RenderLayer import net.minecraft.item.BlockItem import net.minecraft.item.ItemStack +import net.minecraft.item.Items import net.minecraft.item.tooltip.TooltipData import net.minecraft.util.DyeColor import net.minecraft.util.Identifier @@ -80,6 +82,18 @@ object ContainerPreview : Module( private fun getTooltipWidth(): Int = PADDING + COLS * SLOT_SIZE + PADDING private fun getTooltipHeight(): Int = TITLE_HEIGHT + ROWS * SLOT_SIZE + PADDING + /** + * Check if mouse is over the locked tooltip area (for click blocking) + */ + @JvmStatic + fun isMouseOverLockedTooltip(mouseX: Int, mouseY: Int): Boolean { + if (!isLocked) return false + val width = getTooltipWidth() + val height = getTooltipHeight() + return mouseX >= lockedX && mouseX < lockedX + width && + mouseY >= lockedY && mouseY < lockedY + height + } + /** * Calculate text color based on background luminance. * Returns dark text for light backgrounds and white text for dark backgrounds. @@ -97,7 +111,7 @@ object ContainerPreview : Module( } @JvmStatic - fun renderShulkerTooltip(context: DrawContext, textRenderer: TextRenderer, component: ShulkerComponent, mouseX: Int, mouseY: Int) { + fun renderShulkerTooltip(context: DrawContext, textRenderer: TextRenderer, component: ContainerComponent, mouseX: Int, mouseY: Int) { // Calculate tooltip position val width = getTooltipWidth() val height = getTooltipHeight() @@ -144,7 +158,7 @@ object ContainerPreview : Module( } private fun renderTooltipForStack(context: DrawContext, textRenderer: TextRenderer, stack: ItemStack, x: Int, y: Int, allowHover: Boolean) { - val contents = stack.shulkerBoxContents + val contents = getContainerContents(stack) val width = getTooltipWidth() val height = getTooltipHeight() @@ -152,25 +166,20 @@ object ContainerPreview : Module( matrices.push() matrices.translate(0f, 0f, 400f) - // Get shulker box color - val color = getShulkerColor(stack) - val tintColor = if (colorTint && color != null) { - color.entityColor - } else { - 0xFFFFFFFF.toInt() - } + // Get container color (shulker box color or purple for ender chest) + val tintColor = getContainerTintColor(stack) // Draw background with color tint drawBackground(context, x, y, width, height, tintColor) - // Draw title (shulker box name) with appropriate text color + // Draw title (container name) with appropriate text color val name = stack.name val textColor = getTextColor(tintColor) context.drawText(textRenderer, name, x + PADDING, y + 4, textColor, false) - // Draw items - val startX = x + PADDING + 1 - val startY = y + TITLE_HEIGHT + // Slots start at padding offset, items are centered in 18px slots (1px padding on each side) + val slotsStartX = x + PADDING + val slotsStartY = y + TITLE_HEIGHT // Get actual mouse position for hover detection val actualMouseX = (mc.mouse.x * mc.window.scaledWidth / mc.window.width).toInt() @@ -183,19 +192,25 @@ object ContainerPreview : Module( for ((index, item) in contents.withIndex()) { if (index >= COLS * ROWS) break - val slotX = index % COLS - val slotY = index / COLS + val slotCol = index % COLS + val slotRow = index / COLS + + // Slot bounds (full 18x18 area for hover detection) + val slotX = slotsStartX + slotCol * SLOT_SIZE + val slotY = slotsStartY + slotRow * SLOT_SIZE - val itemX = startX + slotX * SLOT_SIZE - val itemY = startY + slotY * SLOT_SIZE + // Item position (centered in slot with 1px offset) + val itemX = slotX + 1 + val itemY = slotY + 1 // Check if this slot is hovered (only when locked/allowHover) + // Use full slot size (18x18) for hover detection to avoid dead zones if (allowHover) { - val isHovered = actualMouseX >= itemX && actualMouseX < itemX + 16 && - actualMouseY >= itemY && actualMouseY < itemY + 16 + val isHovered = actualMouseX >= slotX && actualMouseX < slotX + SLOT_SIZE && + actualMouseY >= slotY && actualMouseY < slotY + SLOT_SIZE if (isHovered && !item.isEmpty) { - // Draw highlight + // Draw highlight on the item area (16x16) context.fill(itemX, itemY, itemX + 16, itemY + 16, 0x80FFFFFF.toInt()) hoveredStack = item hoveredSlotX = actualMouseX @@ -215,17 +230,52 @@ object ContainerPreview : Module( if (hoveredStack != null && allowHover) { matrices.push() matrices.translate(0f, 0f, 500f) // Higher Z than the main tooltip - // Set flag to prevent recursive mixin interception - isRenderingSubTooltip = true - try { - context.drawItemTooltip(textRenderer, hoveredStack, hoveredSlotX, hoveredSlotY) - } finally { - isRenderingSubTooltip = false + + // Check if hovered item is also a previewable container (shulker in echest) + if (isPreviewableContainer(hoveredStack)) { + // Render nested container preview recursively + val nestedWidth = getTooltipWidth() + val nestedHeight = getTooltipHeight() + val nestedX = calculateTooltipX(hoveredSlotX, nestedWidth) + val nestedY = calculateTooltipY(hoveredSlotY, nestedHeight) + renderTooltipForStack(context, textRenderer, hoveredStack, nestedX, nestedY, false) + } else { + // Regular item tooltip - use mixin bypass flag + isRenderingSubTooltip = true + try { + context.drawItemTooltip(textRenderer, hoveredStack, hoveredSlotX, hoveredSlotY) + } finally { + isRenderingSubTooltip = false + } } matrices.pop() } } + private fun getContainerContents(stack: ItemStack): List { + return when { + isShulkerBox(stack) -> stack.shulkerBoxContents + isEnderChest(stack) -> EnderChestContainer.stacks + else -> emptyList() + } + } + + private fun getContainerTintColor(stack: ItemStack): Int { + if (!colorTint) return 0xFFFFFFFF.toInt() + + return when { + isShulkerBox(stack) -> { + val color = getShulkerColor(stack) + color?.entityColor ?: 0xFFFFFFFF.toInt() + } + isEnderChest(stack) -> { + // Ender chest purple/dark tint + 0xFF1E1E2E.toInt() + } + else -> 0xFFFFFFFF.toInt() + } + } + private fun calculateTooltipX(mouseX: Int, width: Int): Int { val screenWidth = mc.window.scaledWidth var x = mouseX + 12 @@ -309,13 +359,31 @@ object ContainerPreview : Module( return stack.item in shulkerBoxes } - class ShulkerComponent(val stack: ItemStack) : TooltipData, TooltipComponent { + @JvmStatic + fun isEnderChest(stack: ItemStack): Boolean { + return stack.item == Items.ENDER_CHEST && EnderChestContainer.stacks.isNotEmpty() + } + + @JvmStatic + fun isPreviewableContainer(stack: ItemStack): Boolean { + return isShulkerBox(stack) || isEnderChest(stack) + } + + open class ContainerComponent(val stack: ItemStack) : TooltipData, TooltipComponent { val contents: List - get() = stack.shulkerBoxContents + get() = when { + isShulkerBox(stack) -> stack.shulkerBoxContents + isEnderChest(stack) -> EnderChestContainer.stacks + else -> emptyList() + } // These methods are not used since we render the tooltip ourselves override fun drawItems(textRenderer: TextRenderer, x: Int, y: Int, width: Int, height: Int, context: DrawContext) {} override fun getHeight(textRenderer: TextRenderer): Int = 0 override fun getWidth(textRenderer: TextRenderer): Int = 0 } + + // Keep for backwards compatibility + @Deprecated("Use ContainerComponent instead", ReplaceWith("ContainerComponent")) + class ShulkerComponent(stack: ItemStack) : ContainerComponent(stack) } diff --git a/src/main/resources/lambda.mixins.common.json b/src/main/resources/lambda.mixins.common.json index 758e2ae26..f11bdfb07 100644 --- a/src/main/resources/lambda.mixins.common.json +++ b/src/main/resources/lambda.mixins.common.json @@ -53,6 +53,7 @@ "render.EntityRendererMixin", "render.FluidRendererMixin", "render.GameRendererMixin", + "render.HandledScreenMixin", "render.GlStateManagerMixin", "render.HeadFeatureRendererMixin", "render.HeldItemRendererMixin", From 92cfe2e14c442fcdc51a436470fc496087d944ff Mon Sep 17 00:00:00 2001 From: Constructor Date: Tue, 9 Dec 2025 19:13:29 +0100 Subject: [PATCH 3/3] Cleanup --- .../lambda/mixin/render/DrawContextMixin.java | 7 +- .../mixin/render/HandledScreenMixin.java | 2 - .../com/lambda/mixin/render/ScreenMixin.java | 1 - .../mixin/render/TooltipComponentMixin.java | 2 - .../module/modules/render/ContainerPreview.kt | 110 +++++------------- 5 files changed, 32 insertions(+), 90 deletions(-) diff --git a/src/main/java/com/lambda/mixin/render/DrawContextMixin.java b/src/main/java/com/lambda/mixin/render/DrawContextMixin.java index 31d7b9589..825e8b9a0 100644 --- a/src/main/java/com/lambda/mixin/render/DrawContextMixin.java +++ b/src/main/java/com/lambda/mixin/render/DrawContextMixin.java @@ -26,6 +26,7 @@ import net.minecraft.item.tooltip.TooltipData; import net.minecraft.text.Text; import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -37,22 +38,18 @@ @Mixin(DrawContext.class) public class DrawContextMixin { @Inject(method = "drawTooltip(Lnet/minecraft/client/font/TextRenderer;Ljava/util/List;Ljava/util/Optional;IILnet/minecraft/util/Identifier;)V", at = @At("HEAD"), cancellable = true) - private void onDrawTooltip(TextRenderer textRenderer, List text, Optional data, int x, int y, Identifier texture, CallbackInfo ci) { + private void onDrawTooltip(TextRenderer textRenderer, List text, Optional data, int x, int y, @Nullable Identifier texture, CallbackInfo ci) { if (!ContainerPreview.INSTANCE.isEnabled()) return; - // Don't intercept if we're rendering a sub-tooltip (prevents infinite recursion) if (ContainerPreview.isRenderingSubTooltip()) return; - // If we're locked, always render our locked tooltip and cancel any other tooltip if (ContainerPreview.isLocked()) { ci.cancel(); ContainerPreview.renderLockedTooltip((DrawContext)(Object)this, textRenderer); return; } - // Check if this is a container tooltip with ContainerPreview if (data.isPresent() && data.get() instanceof ContainerPreview.ContainerComponent component) { - // Cancel the default tooltip and render our custom one ci.cancel(); ContainerPreview.renderShulkerTooltip((DrawContext)(Object)this, textRenderer, component, x, y); } diff --git a/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java b/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java index 7057da433..193cdc516 100644 --- a/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java +++ b/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java @@ -28,7 +28,6 @@ public class HandledScreenMixin { @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) private void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { - // Block clicks when container preview tooltip is locked and mouse is over it if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { if (ContainerPreview.isMouseOverLockedTooltip((int) mouseX, (int) mouseY)) { cir.setReturnValue(true); @@ -38,7 +37,6 @@ private void onMouseClicked(double mouseX, double mouseY, int button, CallbackIn @Inject(method = "mouseReleased", at = @At("HEAD"), cancellable = true) private void onMouseReleased(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { - // Block releases when container preview tooltip is locked and mouse is over it if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { if (ContainerPreview.isMouseOverLockedTooltip((int) mouseX, (int) mouseY)) { cir.setReturnValue(true); diff --git a/src/main/java/com/lambda/mixin/render/ScreenMixin.java b/src/main/java/com/lambda/mixin/render/ScreenMixin.java index 8fb28729e..9c4972653 100644 --- a/src/main/java/com/lambda/mixin/render/ScreenMixin.java +++ b/src/main/java/com/lambda/mixin/render/ScreenMixin.java @@ -51,7 +51,6 @@ private void injectRenderInGameBackground(DrawContext context, CallbackInfo ci) private void wrapRender(Screen instance, DrawContext context, int mouseX, int mouseY, float deltaTicks, Operation original) { original.call(instance, context, mouseX, mouseY, deltaTicks); - // Render locked container preview tooltip after screen rendering if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { ContainerPreview.renderLockedTooltip(context, MinecraftClient.getInstance().textRenderer); } diff --git a/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java b/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java index 3b3d439ef..1088f5c65 100644 --- a/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java +++ b/src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java @@ -33,13 +33,11 @@ public interface TooltipComponentMixin { @Inject(method = "of(Lnet/minecraft/item/tooltip/TooltipData;)Lnet/minecraft/client/gui/tooltip/TooltipComponent;", at = @At("HEAD"), cancellable = true) private static void of(TooltipData tooltipData, CallbackInfoReturnable cir) { - // Handle ContainerPreview container tooltip (shulker boxes and ender chests) if (ContainerPreview.INSTANCE.isEnabled() && tooltipData instanceof ContainerPreview.ContainerComponent containerComponent) { cir.setReturnValue(containerComponent); return; } - // Handle MapPreview map tooltip if (MapPreview.INSTANCE.isEnabled()) cir.setReturnValue((switch (tooltipData) { case MapPreview.MapComponent mapComponent -> mapComponent; case BundleTooltipData bundleTooltipData -> new BundleTooltipComponent(bundleTooltipData.contents()); diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt index 2e3daeed1..04674e1cb 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -50,17 +50,14 @@ object ContainerPreview : Module( private val background = Identifier.ofVanilla("textures/gui/container/shulker_box.png") - // Locked tooltip state - store the stack instead of component for persistence private var lockedStack: ItemStack? = null private var lockedX: Int = 0 private var lockedY: Int = 0 - // Flag to prevent recursive tooltip rendering @JvmStatic var isRenderingSubTooltip: Boolean = false private set - // Tooltip dimensions (3 rows x 9 columns like shulker box) private const val ROWS = 3 private const val COLS = 9 private const val SLOT_SIZE = 18 @@ -79,8 +76,8 @@ object ContainerPreview : Module( return GLFW.glfwGetKey(handle, lockKey.key) == GLFW.GLFW_PRESS } - private fun getTooltipWidth(): Int = PADDING + COLS * SLOT_SIZE + PADDING - private fun getTooltipHeight(): Int = TITLE_HEIGHT + ROWS * SLOT_SIZE + PADDING + private fun getTooltipWidth() = PADDING + COLS * SLOT_SIZE + PADDING + private fun getTooltipHeight() = TITLE_HEIGHT + ROWS * SLOT_SIZE + PADDING /** * Check if mouse is over the locked tooltip area (for click blocking) @@ -102,40 +99,36 @@ object ContainerPreview : Module( val r = ((tintColor shr 16) and 0xFF) / 255f val g = ((tintColor shr 8) and 0xFF) / 255f val b = (tintColor and 0xFF) / 255f - - // Calculate relative luminance using standard formula val luminance = 0.299f * r + 0.587f * g + 0.114f * b - - // Use dark text on light backgrounds, white text on dark backgrounds return if (luminance > 0.5f) 0x404040 else 0xFFFFFF } @JvmStatic - fun renderShulkerTooltip(context: DrawContext, textRenderer: TextRenderer, component: ContainerComponent, mouseX: Int, mouseY: Int) { - // Calculate tooltip position + fun renderShulkerTooltip( + context: DrawContext, + textRenderer: TextRenderer, + component: ContainerComponent, + mouseX: Int, + mouseY: Int + ) { val width = getTooltipWidth() val height = getTooltipHeight() - // Handle locking - lock when key is pressed while hovering a shulker val lockKeyPressed = isLockKeyPressed() if (lockKeyPressed && lockedStack == null) { - // Lock when key is pressed while hovering - lockedStack = component.stack.copy() // Copy the stack to preserve it + lockedStack = component.stack.copy() lockedX = calculateTooltipX(mouseX, width) lockedY = calculateTooltipY(mouseY, height) } else if (!lockKeyPressed && lockedStack != null) { - // Unlock when key is released lockedStack = null } - // If locked, delegate to renderLockedTooltip if (isLocked) { renderLockedTooltipInternal(context, textRenderer) return } - // Not locked - render normal tooltip following mouse renderTooltipForStack(context, textRenderer, component.stack, calculateTooltipX(mouseX, width), calculateTooltipY(mouseY, height), false) } @@ -144,7 +137,6 @@ object ContainerPreview : Module( */ @JvmStatic fun renderLockedTooltip(context: DrawContext, textRenderer: TextRenderer) { - // Update lock state - unlock if key released if (!isLockKeyPressed()) { lockedStack = null return @@ -166,22 +158,16 @@ object ContainerPreview : Module( matrices.push() matrices.translate(0f, 0f, 400f) - // Get container color (shulker box color or purple for ender chest) val tintColor = getContainerTintColor(stack) - // Draw background with color tint drawBackground(context, x, y, width, height, tintColor) - - // Draw title (container name) with appropriate text color val name = stack.name val textColor = getTextColor(tintColor) context.drawText(textRenderer, name, x + PADDING, y + 4, textColor, false) - // Slots start at padding offset, items are centered in 18px slots (1px padding on each side) val slotsStartX = x + PADDING val slotsStartY = y + TITLE_HEIGHT - // Get actual mouse position for hover detection val actualMouseX = (mc.mouse.x * mc.window.scaledWidth / mc.window.width).toInt() val actualMouseY = (mc.mouse.y * mc.window.scaledHeight / mc.window.height).toInt() @@ -195,22 +181,16 @@ object ContainerPreview : Module( val slotCol = index % COLS val slotRow = index / COLS - // Slot bounds (full 18x18 area for hover detection) val slotX = slotsStartX + slotCol * SLOT_SIZE val slotY = slotsStartY + slotRow * SLOT_SIZE - - // Item position (centered in slot with 1px offset) val itemX = slotX + 1 val itemY = slotY + 1 - // Check if this slot is hovered (only when locked/allowHover) - // Use full slot size (18x18) for hover detection to avoid dead zones if (allowHover) { val isHovered = actualMouseX >= slotX && actualMouseX < slotX + SLOT_SIZE && actualMouseY >= slotY && actualMouseY < slotY + SLOT_SIZE if (isHovered && !item.isEmpty) { - // Draw highlight on the item area (16x16) context.fill(itemX, itemY, itemX + 16, itemY + 16, 0x80FFFFFF.toInt()) hoveredStack = item hoveredSlotX = actualMouseX @@ -226,24 +206,20 @@ object ContainerPreview : Module( matrices.pop() - // Draw sub-tooltip for hovered item at higher Z level - if (hoveredStack != null && allowHover) { + hoveredStack?.let { stack -> matrices.push() - matrices.translate(0f, 0f, 500f) // Higher Z than the main tooltip + matrices.translate(0f, 0f, 500f) - // Check if hovered item is also a previewable container (shulker in echest) - if (isPreviewableContainer(hoveredStack)) { - // Render nested container preview recursively + if (isPreviewableContainer(stack)) { val nestedWidth = getTooltipWidth() val nestedHeight = getTooltipHeight() val nestedX = calculateTooltipX(hoveredSlotX, nestedWidth) val nestedY = calculateTooltipY(hoveredSlotY, nestedHeight) - renderTooltipForStack(context, textRenderer, hoveredStack, nestedX, nestedY, false) + renderTooltipForStack(context, textRenderer, stack, nestedX, nestedY, false) } else { - // Regular item tooltip - use mixin bypass flag isRenderingSubTooltip = true try { - context.drawItemTooltip(textRenderer, hoveredStack, hoveredSlotX, hoveredSlotY) + context.drawItemTooltip(textRenderer, stack, hoveredSlotX, hoveredSlotY) } finally { isRenderingSubTooltip = false } @@ -268,10 +244,7 @@ object ContainerPreview : Module( val color = getShulkerColor(stack) color?.entityColor ?: 0xFFFFFFFF.toInt() } - isEnderChest(stack) -> { - // Ender chest purple/dark tint - 0xFF1E1E2E.toInt() - } + isEnderChest(stack) -> 0xFF1E1E2E.toInt() else -> 0xFFFFFFFF.toInt() } } @@ -297,11 +270,6 @@ object ContainerPreview : Module( } private fun drawBackground(context: DrawContext, x: Int, y: Int, width: Int, height: Int, tintColor: Int) { - // Extract RGB components for tinting - val r = ((tintColor shr 16) and 0xFF) / 255f - val g = ((tintColor shr 8) and 0xFF) / 255f - val b = (tintColor and 0xFF) / 255f - // Draw the shulker box texture background with tint // The shulker_box.png texture is 176x166 // Top part (title area): y=0 to y=17 @@ -319,17 +287,17 @@ object ContainerPreview : Module( ) // Middle rows - for (row in 0 until ROWS) { - context.drawTexture( - RenderLayer::getGuiTextured, - background, - x, y + TITLE_HEIGHT + row * SLOT_SIZE, - 0f, 17f, - width, SLOT_SIZE, - 256, 256, - tintColor - ) - } + (0 until ROWS).forEach { row -> + context.drawTexture( + RenderLayer::getGuiTextured, + background, + x, y + TITLE_HEIGHT + row * SLOT_SIZE, + 0f, 17f, + width, SLOT_SIZE, + 256, 256, + tintColor + ) + } // Bottom context.drawTexture( @@ -355,35 +323,17 @@ object ContainerPreview : Module( } @JvmStatic - fun isShulkerBox(stack: ItemStack): Boolean { - return stack.item in shulkerBoxes - } + fun isShulkerBox(stack: ItemStack) = stack.item in shulkerBoxes @JvmStatic - fun isEnderChest(stack: ItemStack): Boolean { - return stack.item == Items.ENDER_CHEST && EnderChestContainer.stacks.isNotEmpty() - } + fun isEnderChest(stack: ItemStack) = stack.item == Items.ENDER_CHEST && EnderChestContainer.stacks.isNotEmpty() @JvmStatic - fun isPreviewableContainer(stack: ItemStack): Boolean { - return isShulkerBox(stack) || isEnderChest(stack) - } + fun isPreviewableContainer(stack: ItemStack) = isShulkerBox(stack) || isEnderChest(stack) open class ContainerComponent(val stack: ItemStack) : TooltipData, TooltipComponent { - val contents: List - get() = when { - isShulkerBox(stack) -> stack.shulkerBoxContents - isEnderChest(stack) -> EnderChestContainer.stacks - else -> emptyList() - } - - // These methods are not used since we render the tooltip ourselves override fun drawItems(textRenderer: TextRenderer, x: Int, y: Int, width: Int, height: Int, context: DrawContext) {} override fun getHeight(textRenderer: TextRenderer): Int = 0 override fun getWidth(textRenderer: TextRenderer): Int = 0 } - - // Keep for backwards compatibility - @Deprecated("Use ContainerComponent instead", ReplaceWith("ContainerComponent")) - class ShulkerComponent(stack: ItemStack) : ContainerComponent(stack) }