Skip to content

Commit fcb458e

Browse files
committed
Ender Chest support and fixes
1 parent d2d41ee commit fcb458e

File tree

7 files changed

+159
-38
lines changed

7 files changed

+159
-38
lines changed

src/main/java/com/lambda/mixin/items/BlockItemMixin.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ public BlockItemMixin(Settings settings) {
3434

3535
@Override
3636
public Optional<TooltipData> getTooltipData(ItemStack stack) {
37-
if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isShulkerBox(stack)) {
38-
return Optional.of(new ContainerPreview.ShulkerComponent(stack));
37+
if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isPreviewableContainer(stack)) {
38+
return Optional.of(new ContainerPreview.ContainerComponent(stack));
3939
}
4040
return super.getTooltipData(stack);
4141
}

src/main/java/com/lambda/mixin/render/DrawContextMixin.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ private void onDrawTooltip(TextRenderer textRenderer, List<Text> text, Optional<
5050
return;
5151
}
5252

53-
// Check if this is a shulker box tooltip with ContainerPreview
54-
if (data.isPresent() && data.get() instanceof ContainerPreview.ShulkerComponent component) {
53+
// Check if this is a container tooltip with ContainerPreview
54+
if (data.isPresent() && data.get() instanceof ContainerPreview.ContainerComponent component) {
5555
// Cancel the default tooltip and render our custom one
5656
ci.cancel();
5757
ContainerPreview.renderShulkerTooltip((DrawContext)(Object)this, textRenderer, component, x, y);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.mixin.render;
19+
20+
import com.lambda.module.modules.render.ContainerPreview;
21+
import net.minecraft.client.gui.screen.ingame.HandledScreen;
22+
import org.spongepowered.asm.mixin.Mixin;
23+
import org.spongepowered.asm.mixin.injection.At;
24+
import org.spongepowered.asm.mixin.injection.Inject;
25+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
26+
27+
@Mixin(HandledScreen.class)
28+
public class HandledScreenMixin {
29+
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
30+
private void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
31+
// Block clicks when container preview tooltip is locked and mouse is over it
32+
if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) {
33+
if (ContainerPreview.isMouseOverLockedTooltip((int) mouseX, (int) mouseY)) {
34+
cir.setReturnValue(true);
35+
}
36+
}
37+
}
38+
39+
@Inject(method = "mouseReleased", at = @At("HEAD"), cancellable = true)
40+
private void onMouseReleased(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
41+
// Block releases when container preview tooltip is locked and mouse is over it
42+
if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) {
43+
if (ContainerPreview.isMouseOverLockedTooltip((int) mouseX, (int) mouseY)) {
44+
cir.setReturnValue(true);
45+
}
46+
}
47+
}
48+
}

src/main/java/com/lambda/mixin/render/ScreenMixin.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import com.lambda.gui.components.QuickSearch;
2121
import com.lambda.module.modules.render.ContainerPreview;
2222
import com.lambda.module.modules.render.NoRender;
23+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
24+
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
2325
import net.minecraft.client.MinecraftClient;
2426
import net.minecraft.client.gui.DrawContext;
2527
import net.minecraft.client.gui.screen.Screen;
@@ -45,9 +47,11 @@ private void injectRenderInGameBackground(DrawContext context, CallbackInfo ci)
4547
if (NoRender.INSTANCE.isEnabled() && NoRender.getNoGuiShadow()) ci.cancel();
4648
}
4749

48-
@Inject(method = "renderWithTooltip", at = @At("TAIL"))
49-
private void onRenderWithTooltip(DrawContext context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) {
50-
// Render locked container preview tooltip at the end of screen rendering
50+
@WrapOperation(method = "renderWithTooltip", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;render(Lnet/minecraft/client/gui/DrawContext;IIF)V"))
51+
private void wrapRender(Screen instance, DrawContext context, int mouseX, int mouseY, float deltaTicks, Operation<Void> original) {
52+
original.call(instance, context, mouseX, mouseY, deltaTicks);
53+
54+
// Render locked container preview tooltip after screen rendering
5155
if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) {
5256
ContainerPreview.renderLockedTooltip(context, MinecraftClient.getInstance().textRenderer);
5357
}

src/main/java/com/lambda/mixin/render/TooltipComponentMixin.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
public interface TooltipComponentMixin {
3434
@Inject(method = "of(Lnet/minecraft/item/tooltip/TooltipData;)Lnet/minecraft/client/gui/tooltip/TooltipComponent;", at = @At("HEAD"), cancellable = true)
3535
private static void of(TooltipData tooltipData, CallbackInfoReturnable<TooltipComponent> cir) {
36-
// Handle ContainerPreview shulker box tooltip
37-
if (ContainerPreview.INSTANCE.isEnabled() && tooltipData instanceof ContainerPreview.ShulkerComponent shulkerComponent) {
38-
cir.setReturnValue(shulkerComponent);
36+
// Handle ContainerPreview container tooltip (shulker boxes and ender chests)
37+
if (ContainerPreview.INSTANCE.isEnabled() && tooltipData instanceof ContainerPreview.ContainerComponent containerComponent) {
38+
cir.setReturnValue(containerComponent);
3939
return;
4040
}
4141

src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.lambda.module.modules.render
1919

2020
import com.lambda.Lambda.mc
2121
import com.lambda.config.settings.complex.Bind
22+
import com.lambda.interaction.material.container.containers.EnderChestContainer
2223
import com.lambda.module.Module
2324
import com.lambda.module.tag.ModuleTag
2425
import com.lambda.util.KeyCode
@@ -32,6 +33,7 @@ import net.minecraft.client.gui.tooltip.TooltipComponent
3233
import net.minecraft.client.render.RenderLayer
3334
import net.minecraft.item.BlockItem
3435
import net.minecraft.item.ItemStack
36+
import net.minecraft.item.Items
3537
import net.minecraft.item.tooltip.TooltipData
3638
import net.minecraft.util.DyeColor
3739
import net.minecraft.util.Identifier
@@ -80,6 +82,18 @@ object ContainerPreview : Module(
8082
private fun getTooltipWidth(): Int = PADDING + COLS * SLOT_SIZE + PADDING
8183
private fun getTooltipHeight(): Int = TITLE_HEIGHT + ROWS * SLOT_SIZE + PADDING
8284

85+
/**
86+
* Check if mouse is over the locked tooltip area (for click blocking)
87+
*/
88+
@JvmStatic
89+
fun isMouseOverLockedTooltip(mouseX: Int, mouseY: Int): Boolean {
90+
if (!isLocked) return false
91+
val width = getTooltipWidth()
92+
val height = getTooltipHeight()
93+
return mouseX >= lockedX && mouseX < lockedX + width &&
94+
mouseY >= lockedY && mouseY < lockedY + height
95+
}
96+
8397
/**
8498
* Calculate text color based on background luminance.
8599
* Returns dark text for light backgrounds and white text for dark backgrounds.
@@ -97,7 +111,7 @@ object ContainerPreview : Module(
97111
}
98112

99113
@JvmStatic
100-
fun renderShulkerTooltip(context: DrawContext, textRenderer: TextRenderer, component: ShulkerComponent, mouseX: Int, mouseY: Int) {
114+
fun renderShulkerTooltip(context: DrawContext, textRenderer: TextRenderer, component: ContainerComponent, mouseX: Int, mouseY: Int) {
101115
// Calculate tooltip position
102116
val width = getTooltipWidth()
103117
val height = getTooltipHeight()
@@ -144,33 +158,28 @@ object ContainerPreview : Module(
144158
}
145159

146160
private fun renderTooltipForStack(context: DrawContext, textRenderer: TextRenderer, stack: ItemStack, x: Int, y: Int, allowHover: Boolean) {
147-
val contents = stack.shulkerBoxContents
161+
val contents = getContainerContents(stack)
148162
val width = getTooltipWidth()
149163
val height = getTooltipHeight()
150164

151165
val matrices = context.matrices
152166
matrices.push()
153167
matrices.translate(0f, 0f, 400f)
154168

155-
// Get shulker box color
156-
val color = getShulkerColor(stack)
157-
val tintColor = if (colorTint && color != null) {
158-
color.entityColor
159-
} else {
160-
0xFFFFFFFF.toInt()
161-
}
169+
// Get container color (shulker box color or purple for ender chest)
170+
val tintColor = getContainerTintColor(stack)
162171

163172
// Draw background with color tint
164173
drawBackground(context, x, y, width, height, tintColor)
165174

166-
// Draw title (shulker box name) with appropriate text color
175+
// Draw title (container name) with appropriate text color
167176
val name = stack.name
168177
val textColor = getTextColor(tintColor)
169178
context.drawText(textRenderer, name, x + PADDING, y + 4, textColor, false)
170179

171-
// Draw items
172-
val startX = x + PADDING + 1
173-
val startY = y + TITLE_HEIGHT
180+
// Slots start at padding offset, items are centered in 18px slots (1px padding on each side)
181+
val slotsStartX = x + PADDING
182+
val slotsStartY = y + TITLE_HEIGHT
174183

175184
// Get actual mouse position for hover detection
176185
val actualMouseX = (mc.mouse.x * mc.window.scaledWidth / mc.window.width).toInt()
@@ -183,19 +192,25 @@ object ContainerPreview : Module(
183192
for ((index, item) in contents.withIndex()) {
184193
if (index >= COLS * ROWS) break
185194

186-
val slotX = index % COLS
187-
val slotY = index / COLS
195+
val slotCol = index % COLS
196+
val slotRow = index / COLS
197+
198+
// Slot bounds (full 18x18 area for hover detection)
199+
val slotX = slotsStartX + slotCol * SLOT_SIZE
200+
val slotY = slotsStartY + slotRow * SLOT_SIZE
188201

189-
val itemX = startX + slotX * SLOT_SIZE
190-
val itemY = startY + slotY * SLOT_SIZE
202+
// Item position (centered in slot with 1px offset)
203+
val itemX = slotX + 1
204+
val itemY = slotY + 1
191205

192206
// Check if this slot is hovered (only when locked/allowHover)
207+
// Use full slot size (18x18) for hover detection to avoid dead zones
193208
if (allowHover) {
194-
val isHovered = actualMouseX >= itemX && actualMouseX < itemX + 16 &&
195-
actualMouseY >= itemY && actualMouseY < itemY + 16
209+
val isHovered = actualMouseX >= slotX && actualMouseX < slotX + SLOT_SIZE &&
210+
actualMouseY >= slotY && actualMouseY < slotY + SLOT_SIZE
196211

197212
if (isHovered && !item.isEmpty) {
198-
// Draw highlight
213+
// Draw highlight on the item area (16x16)
199214
context.fill(itemX, itemY, itemX + 16, itemY + 16, 0x80FFFFFF.toInt())
200215
hoveredStack = item
201216
hoveredSlotX = actualMouseX
@@ -215,17 +230,52 @@ object ContainerPreview : Module(
215230
if (hoveredStack != null && allowHover) {
216231
matrices.push()
217232
matrices.translate(0f, 0f, 500f) // Higher Z than the main tooltip
218-
// Set flag to prevent recursive mixin interception
219-
isRenderingSubTooltip = true
220-
try {
221-
context.drawItemTooltip(textRenderer, hoveredStack, hoveredSlotX, hoveredSlotY)
222-
} finally {
223-
isRenderingSubTooltip = false
233+
234+
// Check if hovered item is also a previewable container (shulker in echest)
235+
if (isPreviewableContainer(hoveredStack)) {
236+
// Render nested container preview recursively
237+
val nestedWidth = getTooltipWidth()
238+
val nestedHeight = getTooltipHeight()
239+
val nestedX = calculateTooltipX(hoveredSlotX, nestedWidth)
240+
val nestedY = calculateTooltipY(hoveredSlotY, nestedHeight)
241+
renderTooltipForStack(context, textRenderer, hoveredStack, nestedX, nestedY, false)
242+
} else {
243+
// Regular item tooltip - use mixin bypass flag
244+
isRenderingSubTooltip = true
245+
try {
246+
context.drawItemTooltip(textRenderer, hoveredStack, hoveredSlotX, hoveredSlotY)
247+
} finally {
248+
isRenderingSubTooltip = false
249+
}
224250
}
225251
matrices.pop()
226252
}
227253
}
228254

255+
private fun getContainerContents(stack: ItemStack): List<ItemStack> {
256+
return when {
257+
isShulkerBox(stack) -> stack.shulkerBoxContents
258+
isEnderChest(stack) -> EnderChestContainer.stacks
259+
else -> emptyList()
260+
}
261+
}
262+
263+
private fun getContainerTintColor(stack: ItemStack): Int {
264+
if (!colorTint) return 0xFFFFFFFF.toInt()
265+
266+
return when {
267+
isShulkerBox(stack) -> {
268+
val color = getShulkerColor(stack)
269+
color?.entityColor ?: 0xFFFFFFFF.toInt()
270+
}
271+
isEnderChest(stack) -> {
272+
// Ender chest purple/dark tint
273+
0xFF1E1E2E.toInt()
274+
}
275+
else -> 0xFFFFFFFF.toInt()
276+
}
277+
}
278+
229279
private fun calculateTooltipX(mouseX: Int, width: Int): Int {
230280
val screenWidth = mc.window.scaledWidth
231281
var x = mouseX + 12
@@ -309,13 +359,31 @@ object ContainerPreview : Module(
309359
return stack.item in shulkerBoxes
310360
}
311361

312-
class ShulkerComponent(val stack: ItemStack) : TooltipData, TooltipComponent {
362+
@JvmStatic
363+
fun isEnderChest(stack: ItemStack): Boolean {
364+
return stack.item == Items.ENDER_CHEST && EnderChestContainer.stacks.isNotEmpty()
365+
}
366+
367+
@JvmStatic
368+
fun isPreviewableContainer(stack: ItemStack): Boolean {
369+
return isShulkerBox(stack) || isEnderChest(stack)
370+
}
371+
372+
open class ContainerComponent(val stack: ItemStack) : TooltipData, TooltipComponent {
313373
val contents: List<ItemStack>
314-
get() = stack.shulkerBoxContents
374+
get() = when {
375+
isShulkerBox(stack) -> stack.shulkerBoxContents
376+
isEnderChest(stack) -> EnderChestContainer.stacks
377+
else -> emptyList()
378+
}
315379

316380
// These methods are not used since we render the tooltip ourselves
317381
override fun drawItems(textRenderer: TextRenderer, x: Int, y: Int, width: Int, height: Int, context: DrawContext) {}
318382
override fun getHeight(textRenderer: TextRenderer): Int = 0
319383
override fun getWidth(textRenderer: TextRenderer): Int = 0
320384
}
385+
386+
// Keep for backwards compatibility
387+
@Deprecated("Use ContainerComponent instead", ReplaceWith("ContainerComponent"))
388+
class ShulkerComponent(stack: ItemStack) : ContainerComponent(stack)
321389
}

src/main/resources/lambda.mixins.common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"render.EntityRendererMixin",
5454
"render.FluidRendererMixin",
5555
"render.GameRendererMixin",
56+
"render.HandledScreenMixin",
5657
"render.GlStateManagerMixin",
5758
"render.HeadFeatureRendererMixin",
5859
"render.HeldItemRendererMixin",

0 commit comments

Comments
 (0)