diff --git a/src/main/java/com/lambda/mixin/gui/MixinGuiChest.java b/src/main/java/com/lambda/mixin/gui/MixinGuiChest.java deleted file mode 100644 index d77f398ad..000000000 --- a/src/main/java/com/lambda/mixin/gui/MixinGuiChest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.lambda.mixin.gui; - -import com.lambda.client.module.modules.render.ContainerPreview; -import net.minecraft.client.gui.inventory.GuiChest; -import net.minecraft.inventory.IInventory; -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; - -@Mixin(GuiChest.class) -public class MixinGuiChest { - - @Inject(method = "", at = @At("RETURN")) - public void drawScreen(IInventory upperInv, IInventory lowerInv, CallbackInfo ci) { - if (lowerInv.getName().equals("Ender Chest")) { - ContainerPreview.INSTANCE.setEnderChest(lowerInv); - } - } -} diff --git a/src/main/java/com/lambda/mixin/gui/MixinGuiScreen.java b/src/main/java/com/lambda/mixin/gui/MixinGuiScreen.java index 7d3aa016c..2c8920a4e 100644 --- a/src/main/java/com/lambda/mixin/gui/MixinGuiScreen.java +++ b/src/main/java/com/lambda/mixin/gui/MixinGuiScreen.java @@ -1,6 +1,5 @@ package com.lambda.mixin.gui; -import com.lambda.client.module.modules.render.ContainerPreview; import com.lambda.client.module.modules.render.MapPreview; import com.lambda.client.module.modules.render.NoRender; import com.lambda.client.util.Wrapper; @@ -25,8 +24,6 @@ public void renderToolTip(ItemStack stack, int x, int y, CallbackInfo ci) { ci.cancel(); MapPreview.drawMap(stack, mapData, x, y); } - } else if (ContainerPreview.INSTANCE.isEnabled()) { - ContainerPreview.INSTANCE.renderTooltips(stack, x, y, ci); } } diff --git a/src/main/java/com/lambda/mixin/network/MixinNetHandlerPlayClient.java b/src/main/java/com/lambda/mixin/network/MixinNetHandlerPlayClient.java index 82a85b304..9bdf478de 100644 --- a/src/main/java/com/lambda/mixin/network/MixinNetHandlerPlayClient.java +++ b/src/main/java/com/lambda/mixin/network/MixinNetHandlerPlayClient.java @@ -2,9 +2,11 @@ import com.lambda.client.event.LambdaEventBus; import com.lambda.client.event.events.ChunkDataEvent; +import com.lambda.client.manager.managers.CachedContainerManager; import net.minecraft.client.multiplayer.WorldClient; import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.network.play.server.SPacketChunkData; +import net.minecraft.network.play.server.SPacketWindowItems; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -20,4 +22,11 @@ public class MixinNetHandlerPlayClient { public void handleChunkData(SPacketChunkData packetIn, CallbackInfo ci) { LambdaEventBus.INSTANCE.post(new ChunkDataEvent(packetIn.isFullChunk(), this.world.getChunk(packetIn.getChunkX(), packetIn.getChunkZ()))); } + + @Inject(method = "handleWindowItems", at = @At(value = "RETURN")) + public void handleItems(SPacketWindowItems packetIn, CallbackInfo ci) { + if (packetIn.getWindowId() != 0) { + CachedContainerManager.updateContainerInventory(packetIn.getWindowId()); + } + } } diff --git a/src/main/java/com/lambda/mixin/player/MixinEntityPlayerSP.java b/src/main/java/com/lambda/mixin/player/MixinEntityPlayerSP.java index a7a8803f0..6a615336c 100644 --- a/src/main/java/com/lambda/mixin/player/MixinEntityPlayerSP.java +++ b/src/main/java/com/lambda/mixin/player/MixinEntityPlayerSP.java @@ -6,6 +6,7 @@ import com.lambda.client.event.events.PlayerMoveEvent; import com.lambda.client.event.events.PushOutOfBlocksEvent; import com.lambda.client.gui.mc.LambdaGuiBeacon; +import com.lambda.client.manager.managers.CachedContainerManager; import com.lambda.client.manager.managers.MessageManager; import com.lambda.client.manager.managers.PlayerPacketManager; import com.lambda.client.module.modules.chat.PortalChat; @@ -18,6 +19,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiChest; import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.entity.MoverType; import net.minecraft.entity.player.EntityPlayer; @@ -68,6 +70,13 @@ public MixinEntityPlayerSP(World worldIn, GameProfile gameProfileIn) { @Shadow protected abstract void updateAutoJump(float p_189810_1_, float p_189810_2_); + @Inject(method = "closeScreen", at = @At("HEAD")) + public void onCloseScreen(CallbackInfo ci) { + if (mc.currentScreen instanceof GuiChest) { + CachedContainerManager.onGuiChestClosed(); + } + } + @Redirect(method = "onLivingUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/entity/EntityPlayerSP;closeScreen()V")) public void closeScreen(EntityPlayerSP player) { if (PortalChat.INSTANCE.isDisabled()) player.closeScreen(); diff --git a/src/main/kotlin/com/lambda/client/event/ForgeEventProcessor.kt b/src/main/kotlin/com/lambda/client/event/ForgeEventProcessor.kt index 6c35b11ee..bcbb1fe7a 100644 --- a/src/main/kotlin/com/lambda/client/event/ForgeEventProcessor.kt +++ b/src/main/kotlin/com/lambda/client/event/ForgeEventProcessor.kt @@ -102,6 +102,16 @@ internal object ForgeEventProcessor { } } + @SubscribeEvent + fun onDrawScreenEvent(event: GuiScreenEvent.DrawScreenEvent) { + LambdaEventBus.post(event) + } + + @SubscribeEvent + fun onRenderTooltipEvent(event: RenderTooltipEvent.Pre) { + LambdaEventBus.post(event) + } + /** * Includes events of subclasses like ChunkEvent and GetCollisionBoxesEvent */ diff --git a/src/main/kotlin/com/lambda/client/gui/hudgui/elements/player/InventoryViewer.kt b/src/main/kotlin/com/lambda/client/gui/hudgui/elements/player/InventoryViewer.kt index cc35214c2..b397482b6 100644 --- a/src/main/kotlin/com/lambda/client/gui/hudgui/elements/player/InventoryViewer.kt +++ b/src/main/kotlin/com/lambda/client/gui/hudgui/elements/player/InventoryViewer.kt @@ -1,12 +1,10 @@ package com.lambda.client.gui.hudgui.elements.player import com.lambda.client.event.SafeClientEvent -import com.lambda.client.event.events.ConnectionEvent import com.lambda.client.event.events.PacketEvent import com.lambda.client.gui.hudgui.HudElement -import com.lambda.client.mixin.extension.windowID -import com.lambda.client.module.modules.client.ClickGUI -import com.lambda.client.module.modules.client.GuiColors +import com.lambda.client.manager.managers.CachedContainerManager +import com.lambda.client.util.color.ColorHolder import com.lambda.client.util.graphics.GlStateUtils import com.lambda.client.util.graphics.RenderUtils2D import com.lambda.client.util.graphics.VertexHelper @@ -14,15 +12,9 @@ import com.lambda.client.util.items.storageSlots import com.lambda.client.util.math.Vec2d import com.lambda.client.util.threads.runSafe import com.lambda.client.util.threads.safeListener -import net.minecraft.client.gui.inventory.GuiContainer import net.minecraft.client.renderer.GlStateManager import net.minecraft.client.renderer.Tessellator import net.minecraft.client.renderer.vertex.DefaultVertexFormats -import net.minecraft.init.Blocks -import net.minecraft.inventory.ContainerChest -import net.minecraft.inventory.InventoryBasic -import net.minecraft.item.ItemStack -import net.minecraft.network.play.client.CPacketCloseWindow import net.minecraft.network.play.server.SPacketOpenWindow import net.minecraft.util.ResourceLocation import net.minecraft.util.text.TextComponentTranslation @@ -38,22 +30,21 @@ internal object InventoryViewer : HudElement( private val showIcon by setting("Show Icon", false, { !mcTexture }) private val iconScale by setting("Icon Scale", 0.5f, 0.1f..1.0f, 0.1f, { !mcTexture && showIcon }) private val background by setting("Background", true, { !mcTexture }) - private val alpha by setting("Alpha", 150, 0..255, 1, { !mcTexture }) + private val backgroundColor by setting("Background Color", ColorHolder(0, 0, 0, 150), visibility = { !mcTexture && background }) + private val outline by setting("Outline", true, visibility = { !mcTexture }) + private val outlineColor by setting("Outline Color", ColorHolder(255, 255, 255, 150), visibility = { !mcTexture && outline }) + private val outlineThickness by setting("Outline Thickness", 1.0f, 0.5f..5.0f, 0.5f, { !mcTexture && outline }) private val containerTexture = ResourceLocation("textures/gui/container/inventory.png") private val lambdaIcon = ResourceLocation("lambda/lambda_icon.png") - private var enderChestContents: MutableList = MutableList(27) { ItemStack(Blocks.AIR) } override val hudWidth: Float = 162.0f override val hudHeight: Float = 54.0f - private var openedEnderChest: Int = -1 - override fun renderHud(vertexHelper: VertexHelper) { super.renderHud(vertexHelper) runSafe { drawFrame(vertexHelper) drawFrameTexture() - checkEnderChest() drawItems() } } @@ -61,10 +52,10 @@ internal object InventoryViewer : HudElement( private fun drawFrame(vertexHelper: VertexHelper) { if (!mcTexture) { if (background) { - RenderUtils2D.drawRectFilled(vertexHelper, posEnd = Vec2d(162.0, 54.0), color = GuiColors.backGround.apply { a = alpha }) + RenderUtils2D.drawRectFilled(vertexHelper, posEnd = Vec2d(hudWidth.toDouble(), hudHeight.toDouble()), color = backgroundColor) } - if (ClickGUI.windowOutline) { - RenderUtils2D.drawRectOutline(vertexHelper, posEnd = Vec2d(162.0, 54.0), lineWidth = ClickGUI.outlineWidth, color = GuiColors.outline.apply { a = alpha }) + if (outline) { + RenderUtils2D.drawRectOutline(vertexHelper, posEnd = Vec2d(hudWidth.toDouble(), hudHeight.toDouble()), lineWidth = outlineThickness, color = outlineColor) } } } @@ -78,15 +69,15 @@ internal object InventoryViewer : HudElement( mc.renderEngine.bindTexture(containerTexture) buffer.begin(GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX) buffer.pos(0.0, 0.0, 0.0).tex(0.02734375, 0.32421875).endVertex() // (7 / 256), (83 / 256) - buffer.pos(0.0, 54.0, 0.0).tex(0.02734375, 0.53125).endVertex() // (7 / 256), (136 / 256) - buffer.pos(162.0, 0.0, 0.0).tex(0.65625, 0.32421875).endVertex() // (168 / 256), (83 / 256) - buffer.pos(162.0, 54.0, 0.0).tex(0.65625, 0.53125).endVertex() // (168 / 256), (136 / 256) + buffer.pos(0.0, hudHeight.toDouble(), 0.0).tex(0.02734375, 0.53125).endVertex() // (7 / 256), (136 / 256) + buffer.pos(hudWidth.toDouble(), 0.0, 0.0).tex(0.65625, 0.32421875).endVertex() // (168 / 256), (83 / 256) + buffer.pos(hudWidth.toDouble(), hudHeight.toDouble(), 0.0).tex(0.65625, 0.53125).endVertex() // (168 / 256), (136 / 256) tessellator.draw() } else if (showIcon) { mc.renderEngine.bindTexture(lambdaIcon) GlStateManager.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - val center = Vec2d(81.0, 27.0) + val center = Vec2d(hudWidth / 2.0, hudHeight / 2.0) val halfWidth = iconScale * 50.0 val halfHeight = iconScale * 50.0 @@ -103,10 +94,6 @@ internal object InventoryViewer : HudElement( init { - safeListener { - openedEnderChest = -1 - } - safeListener { if (it.packet !is SPacketOpenWindow) return@safeListener if (it.packet.guiId != "minecraft:container") return@safeListener @@ -114,50 +101,26 @@ internal object InventoryViewer : HudElement( if (title !is TextComponentTranslation) return@safeListener if (title.key != "container.enderchest") return@safeListener - openedEnderChest = it.packet.windowId - } - - safeListener { - if (it.packet !is CPacketCloseWindow) return@safeListener - if (it.packet.windowID != openedEnderChest) return@safeListener - - checkEnderChest() - openedEnderChest = -1 - } - } - - private fun checkEnderChest() { - val guiScreen = mc.currentScreen - - if (guiScreen !is GuiContainer) return - - val container = guiScreen.inventorySlots - - if (container is ContainerChest && container.lowerChestInventory is InventoryBasic) { - if (container.windowId == openedEnderChest) { - for (i in 0..26) enderChestContents[i] = container.inventory[i] - } } } private fun SafeClientEvent.drawItems() { if (enderChest == SlotType.ENDER_CHEST) { - for ((index, stack) in enderChestContents.withIndex()) { - if (stack.isEmpty) continue - - val slotX = index % 9 * 18 + 1 - val slotY = index / 9 * 18 + 1 - RenderUtils2D.drawItem(stack, slotX, slotY) + CachedContainerManager.getEnderChestInventory().forEachIndexed { index, stack -> + if (stack.isEmpty) return@forEachIndexed + val slotX = index % 9 * (hudWidth / 9.0) + 1 + val slotY = index / 9 * (hudWidth / 9.0) + 1 + RenderUtils2D.drawItem(stack, slotX.toInt(), slotY.toInt()) } } else { for ((index, slot) in player.storageSlots.withIndex()) { val itemStack = slot.stack if (itemStack.isEmpty) continue - val slotX = index % 9 * 18 + 1 - val slotY = index / 9 * 18 + 1 + val slotX = index % 9 * (hudWidth / 9.0) + 1 + val slotY = index / 9 * (hudWidth / 9.0) + 1 - RenderUtils2D.drawItem(itemStack, slotX, slotY) + RenderUtils2D.drawItem(itemStack, slotX.toInt(), slotY.toInt()) } } } diff --git a/src/main/kotlin/com/lambda/client/manager/managers/CachedContainerManager.kt b/src/main/kotlin/com/lambda/client/manager/managers/CachedContainerManager.kt new file mode 100644 index 000000000..7b451eda6 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/manager/managers/CachedContainerManager.kt @@ -0,0 +1,137 @@ +package com.lambda.client.manager.managers + +import com.lambda.client.LambdaMod +import com.lambda.client.event.events.ConnectionEvent +import com.lambda.client.event.listener.listener +import com.lambda.client.manager.Manager +import com.lambda.client.module.modules.player.PacketLogger +import com.lambda.client.module.modules.render.ContainerPreview.cacheEnderChests +import com.lambda.client.util.FolderUtils +import com.lambda.client.util.threads.defaultScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import net.minecraft.inventory.ContainerChest +import net.minecraft.inventory.IInventory +import net.minecraft.inventory.InventoryBasic +import net.minecraft.inventory.ItemStackHelper +import net.minecraft.item.ItemStack +import net.minecraft.nbt.CompressedStreamTools +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.util.NonNullList +import java.io.File +import java.io.IOException +import java.nio.file.Paths + +object CachedContainerManager : Manager { + private val directory = Paths.get(FolderUtils.lambdaFolder, "cached-containers").toFile() + private var echestFile: File? = null + private var currentEnderChest: NonNullList? = null + + init { + listener { + val serverDirectory = if (mc.integratedServer != null && mc.integratedServer?.isServerRunning == true) { + mc.integratedServer?.folderName ?: run { + LambdaMod.LOG.info("Failed to get SP directory") + return@listener + } + } else { + mc.currentServerData?.serverIP?.replace(":", "_") + ?: run { + LambdaMod.LOG.info("Failed to get server directory") + return@listener + } + } + + val folder = File(directory, serverDirectory) + echestFile = folder.toPath().resolve(mc.session.profile.id.toString()).resolve("echest.nbt").toFile() + + echestFile?.let { file -> + try { + if (!file.exists()) { + if (!file.parentFile.exists()) file.parentFile.mkdirs() + file.createNewFile() + } + } catch (e: IOException) { + LambdaMod.LOG.error("Failed to create ender chest file", e) + } + } + } + + listener { + echestFile = null + currentEnderChest = null + } + } + + fun getEnderChestInventory(): NonNullList { + echestFile?.let { eFile -> + currentEnderChest?.let { return it } + if (!cacheEnderChests) return@let + try { + CompressedStreamTools.read(eFile)?.let { nbt -> + val inventory = NonNullList.withSize(27, ItemStack.EMPTY) + ItemStackHelper.loadAllItems(nbt, inventory) + currentEnderChest = inventory + return inventory + } + } catch (e: IOException) { + currentEnderChest = NonNullList.withSize(27, ItemStack.EMPTY) + LambdaMod.LOG.warn("${PacketLogger.chatName} Failed loading echest!", e) + } + + } + return NonNullList.withSize(27, ItemStack.EMPTY) + } + + private fun saveEchest(inventory: NonNullList) { + if (!cacheEnderChests) return + val currentEchestFile = echestFile ?: return + val nonNullList = NonNullList.withSize(inventory.size, ItemStack.EMPTY) + inventory.forEachIndexed { index, itemStack -> + nonNullList[index] = itemStack + } + val nbt = NBTTagCompound() + ItemStackHelper.saveAllItems(nbt, nonNullList) + + defaultScope.launch(Dispatchers.IO) { + try { + CompressedStreamTools.write(nbt, currentEchestFile) + } catch (e: Throwable) { + LambdaMod.LOG.warn("${PacketLogger.chatName} Failed saving echest!", e) + } + } + } + + @JvmStatic + fun setEnderChestInventory(inv: IInventory) { + val inventory = NonNullList.withSize(inv.sizeInventory, ItemStack.EMPTY) + for (i in 0 until inv.sizeInventory) { + inventory[i] = inv.getStackInSlot(i) + } + currentEnderChest = inventory + saveEchest(inventory) + } + + @JvmStatic + fun updateContainerInventory(windowId: Int) { + val container = mc.player.openContainer + if (container.windowId != windowId) return + if (container !is ContainerChest) return + val chest = container.lowerChestInventory + if (chest !is InventoryBasic) return + if (chest.name.contains("Ender Chest")) + setEnderChestInventory(chest) + // we can save other container inventories here but we also need the position + } + + @JvmStatic + fun onGuiChestClosed() { + val container = mc.player.openContainer + if (container !is ContainerChest) return + val chest = container.lowerChestInventory + if (chest !is InventoryBasic) return + if (chest.name.contains("Ender Chest")) + setEnderChestInventory(chest) + // we can save other container inventories here but we also need the position + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/client/module/modules/render/ContainerPreview.kt index 53ac4006d..4c2d25a65 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/render/ContainerPreview.kt @@ -1,140 +1,374 @@ package com.lambda.client.module.modules.render -import com.lambda.client.commons.extension.ceilToInt +import com.lambda.client.commons.extension.floorToInt +import com.lambda.client.event.events.RenderOverlayEvent +import com.lambda.client.manager.managers.CachedContainerManager import com.lambda.client.module.Category import com.lambda.client.module.Module +import com.lambda.client.util.Bind import com.lambda.client.util.color.ColorHolder import com.lambda.client.util.graphics.GlStateUtils +import com.lambda.client.util.graphics.ProjectionUtils import com.lambda.client.util.graphics.RenderUtils2D import com.lambda.client.util.graphics.VertexHelper import com.lambda.client.util.graphics.font.FontRenderAdapter import com.lambda.client.util.items.block import com.lambda.client.util.math.Vec2d +import com.lambda.client.util.threads.safeListener +import net.minecraft.block.BlockEnderChest +import net.minecraft.block.BlockShulkerBox +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.ScaledResolution +import net.minecraft.client.gui.inventory.GuiContainer import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.RenderHelper +import net.minecraft.entity.item.EntityItemFrame +import net.minecraft.entity.player.EntityPlayer import net.minecraft.init.Blocks +import net.minecraft.inventory.Container import net.minecraft.inventory.IInventory +import net.minecraft.inventory.ItemStackHelper +import net.minecraft.inventory.Slot import net.minecraft.item.ItemShulkerBox import net.minecraft.item.ItemStack -import net.minecraft.nbt.NBTTagCompound +import net.minecraft.util.NonNullList +import net.minecraft.util.math.Vec3d +import net.minecraft.util.text.ITextComponent +import net.minecraft.util.text.TextComponentString +import net.minecraftforge.client.event.GuiScreenEvent +import net.minecraftforge.client.event.RenderTooltipEvent +import org.lwjgl.input.Keyboard import org.lwjgl.opengl.GL11.GL_LINE_LOOP import org.lwjgl.opengl.GL11.glLineWidth -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo object ContainerPreview : Module( name = "ContainerPreview", - description = "Previews shulkers and ender chests in the game GUI", + description = "Previews shulkers and ender chests in inventories or item frames", category = Category.RENDER ) { - private val useCustomFont by setting("Use Custom Font", false) + val cacheEnderChests by setting("Cache Ender Chest", true) private val backgroundColor by setting("Background Color", ColorHolder(16, 0, 16, 255)) private val borderTopColor by setting("Top Border Color", ColorHolder(144, 101, 237, 54)) private val borderBottomColor by setting("Bottom Border Color", ColorHolder(40, 0, 127, 80)) + private val previewLock by setting("Preview Lock Bind", Bind(Keyboard.KEY_LSHIFT)) + private val itemFrames by setting("Item Frames", true) - var enderChest: IInventory? = null + private const val previewWidth = 176 + private const val previewHeight = 70 + private var isMouseInPreviewGui = false + private var isPreviewTooltip = false - fun renderTooltips(itemStack: ItemStack, x: Int, y: Int, ci: CallbackInfo) { - val item = itemStack.item + private var stackContainer: GuiPreview? = null - if (item is ItemShulkerBox) { - renderShulkerBoxTooltips(itemStack, x, y, ci) - } else if (item.block == Blocks.ENDER_CHEST) { - renderEnderChestTooltips(itemStack, x, y, ci) + init { + safeListener { + if (mc.currentScreen !is GuiContainer) return@safeListener + val gui = it.gui as GuiContainer + + if (!Keyboard.isKeyDown(previewLock.key)) { + gui.slotUnderMouse?.let { slotUnder -> + if (slotUnder.hasStack + && !slotUnder.stack.isEmpty + && (slotUnder.stack.item is ItemShulkerBox || slotUnder.stack.item.block == Blocks.ENDER_CHEST) + ) { + if (stackContainer == null || stackContainer?.parentContainer != slotUnder.stack) { + stackContainer = createPreviewGui(slotUnder.stack, getContainerContents(slotUnder.stack)) + } + } else { + stackContainer = null + } + + stackContainer?.let { sc -> + val res = ScaledResolution(mc) + // ensure the preview gui is on screen + val dX = it.mouseX + 8 + val previewDrawX = if (dX + previewWidth > res.scaledWidth) { + res.scaledWidth - previewWidth + } else dX + val dY = it.mouseY + val previewDrawY = if (dY + previewHeight > res.scaledHeight) { + res.scaledHeight - previewHeight + } else dY + sc.posX = previewDrawX + sc.posY = previewDrawY + } + } ?: run { stackContainer = null } + } + stackContainer?.drawScreen(it.mouseX, it.mouseY, it.renderPartialTicks) } - } - private fun renderShulkerBoxTooltips(itemStack: ItemStack, x: Int, y: Int, ci: CallbackInfo) { - getShulkerData(itemStack)?.let { - val itemStacks = Array(27) { ItemStack.EMPTY } - val nbtTagList = it.getTagList("Items", 10) + safeListener { + if (mc.currentScreen !is GuiContainer + || isPreviewTooltip + || !(isMouseInPreviewGui + || it.stack.item is ItemShulkerBox + || it.stack.item.block == Blocks.ENDER_CHEST)) return@safeListener + it.isCanceled = true + } + + safeListener { + if (!itemFrames) return@safeListener + mc.renderManager.pointedEntity?.let { pe -> + if (pe !is EntityItemFrame) return@safeListener + if (!(pe.displayedItem.item.block is BlockShulkerBox + || pe.displayedItem.item.block is BlockEnderChest)) return@safeListener + val posX = pe.posX + (pe.facingDirection?.xOffset ?: 0) * 0.1 + val posY = pe.posY + (pe.facingDirection?.yOffset ?: 0) * 0.1 + val posZ = pe.posZ + (pe.facingDirection?.zOffset ?: 0) * 0.1 + val screenPos = ProjectionUtils.toScaledScreenPos(Vec3d(posX, posY, posZ)) + val width = 9 * 16 + val height = 3 * 16 + val newX = screenPos.x - width / 2 + val newY = screenPos.y - height / 2 + + GlStateManager.pushMatrix() + val vertexHelper = VertexHelper(GlStateUtils.useVbo()) + RenderUtils2D.drawRoundedRectFilled( + vertexHelper, + Vec2d(newX, newY), + Vec2d(newX + width, newY + height), + 1.0, + color = backgroundColor + ) + + drawRectOutline(vertexHelper, newX + 1.0, newY + 1.0, (width - 2).toDouble(), (height - 2).toDouble()) + GlStateManager.enableDepth() - for (i in 0 until nbtTagList.tagCount()) { - val itemStackNBTTag = nbtTagList.getCompoundTagAt(i) - val slot = itemStackNBTTag.getInteger("Slot") and 255 - if (slot in itemStacks.indices) { - itemStacks[slot] = ItemStack(itemStackNBTTag) + RenderHelper.enableGUIStandardItemLighting() + GlStateManager.enableRescaleNormal() + GlStateManager.enableColorMaterial() + GlStateManager.enableLighting() + val contents = if (pe.displayedItem.item.block is BlockShulkerBox && pe.displayedItem.hasTagCompound()) + getContainerContents(pe.displayedItem) + else if (pe.displayedItem.item.block is BlockEnderChest) getEnderChestData() + else null + contents?.forEachIndexed { index, itemStack -> + val x = newX + (index % 9) * 16 + val y = newY + (index / 9) * 16 + RenderUtils2D.drawItem(itemStack, x.floorToInt(), y.floorToInt()) } + GlStateManager.popMatrix() } + } + } - ci.cancel() - renderContainerAndItems(itemStack, x, y, itemStacks) + private fun createPreviewGui( + parentContainer: ItemStack, + containerContents: MutableList + ): GuiPreview { + return GuiPreview( + PreviewContainer(PreviewInventory(containerContents), 27), + parentContainer + ) + } + + private fun getContainerContents(stack: ItemStack): MutableList { // TODO: move somewhere else + return if (stack.item.block == Blocks.ENDER_CHEST) { + getEnderChestData() + } else { + val contents = NonNullList.withSize(27, ItemStack.EMPTY) + val compound = stack.tagCompound + if (compound != null && compound.hasKey("BlockEntityTag", 10)) { + val tags = compound.getCompoundTag("BlockEntityTag") + if (tags.hasKey("Items", 9)) ItemStackHelper.loadAllItems(tags, contents) + } + contents } } - private fun getShulkerData(stack: ItemStack): NBTTagCompound? { - val tagCompound = if (stack.item is ItemShulkerBox) stack.tagCompound else return null + private fun drawRectOutline(vertexHelper: VertexHelper, x: Double, y: Double, width: Double, height: Double) { + RenderUtils2D.prepareGl() + glLineWidth(5.0f) + + vertexHelper.begin(GL_LINE_LOOP) + vertexHelper.put(Vec2d(x, y), borderTopColor) + vertexHelper.put(Vec2d(x, y + height), borderBottomColor) + vertexHelper.put(Vec2d(x + width, y + height), borderBottomColor) + vertexHelper.put(Vec2d(x + width, y), borderTopColor) + vertexHelper.end() - tagCompound?.let { - val blockEntityTag = it.getCompoundTag("BlockEntityTag") - if (blockEntityTag.hasKey("Items", 9)) { - return blockEntityTag + RenderUtils2D.releaseGl() + glLineWidth(1.0f) + } + + private fun getEnderChestData(): MutableList { + return CachedContainerManager.getEnderChestInventory().toMutableList() + } + + class GuiPreview(inventorySlotsIn: Container, val parentContainer: ItemStack) : GuiContainer(inventorySlotsIn) { + var posX: Int = 0 + var posY: Int = 0 + + init { + this.mc = Minecraft.getMinecraft() + this.fontRenderer = this.mc.fontRenderer + this.width = mc.displayWidth + this.height = mc.displayHeight + } + + override fun drawScreen(mouseX: Int, mouseY: Int, partialTicks: Float) { + drawPreview(posX, posY, parentContainer, inventorySlots.inventorySlots.map { it.stack }, 301) + + var hoveringOver: Slot? = null + val rx = posX.toDouble() + 8 + val ry = posY.toDouble() - 5 + + for (slot in inventorySlots.inventorySlots) { + if (!slot.hasStack) continue + val px = rx + slot.xPos + val py = ry + slot.yPos + if (isPointInRegion(px.toInt(), py.toInt(), 16, 16, mouseX, mouseY)) + hoveringOver = slot } + if (hoveringOver != null) + drawHoveredItem((rx + hoveringOver.xPos).toInt(), (ry + hoveringOver.yPos).toInt(), hoveringOver) + + isMouseInPreviewGui = isPointInRegion(posX, posY, xSize, ySize, mouseX, mouseY) + + GlStateManager.disableBlend() + GlStateManager.color(1f, 1f, 1f, 1.0f) + } + + private fun drawHoveredItem(drawX: Int, drawY: Int, hoveringOver: Slot) { + GlStateManager.disableLighting() + GlStateManager.disableDepth() + GlStateManager.colorMask(true, true, true, false) + drawGradientRect( + drawX, + drawY, + drawX + 16, + drawY + 16, + -2130706433, + -2130706433) + GlStateManager.colorMask(true, true, true, true) + GlStateManager.enableDepth() + + if (hoveringOver.stack.item is ItemShulkerBox || hoveringOver.stack.item.block == Blocks.ENDER_CHEST) { + val res = ScaledResolution(mc) + // ensure the preview gui is on screen + val dX = drawX + 16 + val previewDrawX = if (dX + previewWidth > res.scaledWidth) dX - previewWidth else dX + val dY = drawY + 8 + val previewDrawY = if (dY + previewHeight > res.scaledHeight) dY - previewHeight else dY + drawPreview(previewDrawX, previewDrawY, hoveringOver.stack, getContainerContents(hoveringOver.stack), 400) + } else { + isPreviewTooltip = true + renderToolTip(hoveringOver.stack, drawX + 8, drawY + 8) + isPreviewTooltip = false + } + } + + private fun drawPreview(drawX: Int, drawY: Int, container: ItemStack, containerContents: List, z: Int) { + val depth = z.toDouble() + val x = drawX.toDouble() + val y = drawY.toDouble() + + val vertexHelper = VertexHelper(GlStateUtils.useVbo()) + GlStateManager.disableDepth() + RenderUtils2D.drawRoundedRectFilled( + vertexHelper, + Vec2d(x, y), + Vec2d(x + previewWidth, y + previewHeight), + 1.0, + color = backgroundColor + ) + drawRectOutline(vertexHelper, x + 1.0, y + 1.0, (previewWidth - 2).toDouble(), (previewHeight - 2.toFloat()).toDouble()) + FontRenderAdapter.drawString(container.displayName, (x + 4).toFloat(), (y + 2).toFloat()) + GlStateManager.enableDepth() + RenderHelper.enableGUIStandardItemLighting() + GlStateManager.enableRescaleNormal() + GlStateManager.enableColorMaterial() + GlStateManager.enableLighting() + for (slot in inventorySlots.inventorySlots) { + val px = x + 8 + slot.xPos + val py = y - 5 + slot.yPos + RenderUtils2D.drawItem(containerContents[slot.slotIndex], px.toInt(), py.toInt(), z = (depth + 1).toFloat()) + } + GlStateManager.disableLighting() + } + + override fun drawGuiContainerBackgroundLayer(partialTicks: Float, mouseX: Int, mouseY: Int) { + // do nothing } - return null } - private fun renderEnderChestTooltips(itemStack: ItemStack, x: Int, y: Int, ci: CallbackInfo) { - val itemStacks = Array(27) { ItemStack.EMPTY } - enderChest?.let { - for (i in itemStacks.indices) { - itemStacks[i] = it.getStackInSlot(i) + class PreviewContainer(val inventory: PreviewInventory, val size: Int) : Container() { + init { + for (i in 0 until size) { + addSlotToContainer(Slot(inventory, i, i % 9 * 18, (i / 9 + 1) * 18 + 1)) } } - ci.cancel() - renderContainerAndItems(itemStack, x, y, itemStacks) + override fun canInteractWith(playerIn: EntityPlayer): Boolean { + return false + } } - private fun renderContainerAndItems(stack: ItemStack, originalX: Int, originalY: Int, items: Array) { - GlStateManager.pushMatrix() - GlStateManager.translate(0.0, 0.0, 500.0) + class PreviewInventory(private val contents: MutableList) : IInventory { + override fun getName(): String { + return "Preview Inventory" + } - renderContainer(stack, originalX, originalY) - renderContainerItems(items, originalX, originalY) + override fun hasCustomName(): Boolean { + return false + } - GlStateManager.popMatrix() - } + override fun getDisplayName(): ITextComponent { + return TextComponentString("Preview Inventory") + } - private fun renderContainer(stack: ItemStack, originalX: Int, originalY: Int) { - val width = 144.coerceAtLeast(FontRenderAdapter.getStringWidth(stack.displayName).ceilToInt() + 3) - val vertexHelper = VertexHelper(GlStateUtils.useVbo()) + override fun getSizeInventory(): Int { + return contents.size + } - val x = (originalX + 12).toDouble() - val y = (originalY - 12).toDouble() - val height = FontRenderAdapter.getFontHeight(customFont = useCustomFont) + 48 + override fun isEmpty(): Boolean { + return contents.isEmpty() + } - RenderUtils2D.drawRoundedRectFilled( - vertexHelper, - Vec2d(x - 4, y - 4), - Vec2d(x + width + 4, y + height + 4), - 1.0, - color = backgroundColor - ) + override fun getStackInSlot(index: Int): ItemStack { + return contents[index] + } - drawRectOutline(vertexHelper, x, y, width, height) + override fun decrStackSize(index: Int, count: Int): ItemStack { + return ItemStack.EMPTY + } - FontRenderAdapter.drawString(stack.displayName, x.toFloat(), y.toFloat() - 2.0f, customFont = useCustomFont) - } + override fun removeStackFromSlot(index: Int): ItemStack { + return ItemStack.EMPTY + } - private fun drawRectOutline(vertexHelper: VertexHelper, x: Double, y: Double, width: Int, height: Float) { - RenderUtils2D.prepareGl() - glLineWidth(5.0f) + override fun setInventorySlotContents(index: Int, stack: ItemStack) { + this.contents[index] = stack + } - vertexHelper.begin(GL_LINE_LOOP) - vertexHelper.put(Vec2d(x - 3, y - 3), borderTopColor) - vertexHelper.put(Vec2d(x - 3, y + height + 3), borderBottomColor) - vertexHelper.put(Vec2d(x + width + 3, y + height + 3), borderBottomColor) - vertexHelper.put(Vec2d(x + width + 3, y - 3), borderTopColor) - vertexHelper.end() + override fun getInventoryStackLimit(): Int { + return 27 + } - RenderUtils2D.releaseGl() - glLineWidth(1.0f) - } + override fun markDirty() {} + + override fun isUsableByPlayer(player: EntityPlayer): Boolean { + return false + } + + override fun openInventory(player: EntityPlayer) {} + + override fun closeInventory(player: EntityPlayer) {} + + override fun isItemValidForSlot(index: Int, stack: ItemStack): Boolean { + return true + } - private fun renderContainerItems(itemStacks: Array, originalX: Int, originalY: Int) { - for (i in itemStacks.indices) { - val x = originalX + (i % 9) * 16 + 11 - val y = originalY + (i / 9) * 16 - 2 - RenderUtils2D.drawItem(itemStacks[i], x, y) + override fun getField(id: Int): Int { + return 0 } + + override fun setField(id: Int, value: Int) {} + + override fun getFieldCount(): Int { + return 0 + } + + override fun clear() {} } } diff --git a/src/main/kotlin/com/lambda/client/util/graphics/RenderUtils2D.kt b/src/main/kotlin/com/lambda/client/util/graphics/RenderUtils2D.kt index 5a86fb8c6..7c730e495 100644 --- a/src/main/kotlin/com/lambda/client/util/graphics/RenderUtils2D.kt +++ b/src/main/kotlin/com/lambda/client/util/graphics/RenderUtils2D.kt @@ -16,12 +16,12 @@ import kotlin.math.* object RenderUtils2D { val mc = Wrapper.minecraft - fun drawItem(itemStack: ItemStack, x: Int, y: Int, text: String? = null, drawOverlay: Boolean = true) { + fun drawItem(itemStack: ItemStack, x: Int, y: Int, text: String? = null, drawOverlay: Boolean = true, z: Float = 0.0f) { GlStateUtils.blend(true) GlStateUtils.depth(true) RenderHelper.enableGUIStandardItemLighting() - mc.renderItem.zLevel = 0.0f + mc.renderItem.zLevel = z mc.renderItem.renderItemAndEffectIntoGUI(itemStack, x, y) if (drawOverlay) mc.renderItem.renderItemOverlayIntoGUI(mc.fontRenderer, itemStack, x, y, text) mc.renderItem.zLevel = 0.0f diff --git a/src/main/resources/mixins.lambda.json b/src/main/resources/mixins.lambda.json index 6da5206ac..d92b2be59 100644 --- a/src/main/resources/mixins.lambda.json +++ b/src/main/resources/mixins.lambda.json @@ -42,7 +42,6 @@ "entity.MixinEntityLlama", "entity.MixinEntityPig", "gui.MixinGuiChat", - "gui.MixinGuiChest", "gui.MixinGuiContainer", "gui.MixinGuiIngameForge", "gui.MixinGuiIngameMenu",