diff --git a/src/main/java/codechicken/nei/ItemsGrid.java b/src/main/java/codechicken/nei/ItemsGrid.java index dc0777c4..e8595585 100644 --- a/src/main/java/codechicken/nei/ItemsGrid.java +++ b/src/main/java/codechicken/nei/ItemsGrid.java @@ -19,6 +19,7 @@ import org.lwjgl.opengl.GL30; import codechicken.lib.vec.Rectangle4i; import codechicken.nei.api.GuiInfo; +import codechicken.nei.api.INEIGuiHandler; import codechicken.nei.guihook.GuiContainerManager; import codechicken.nei.recipe.Recipe.RecipeId; import codechicken.nei.recipe.StackInfo; @@ -348,16 +349,24 @@ public abstract class ItemsGrid= 0 && idx < invalidSlotMap.length && !invalidSlotMap[idx] - && GuiInfo.hideItemPanelSlot(gui, getSlotRect(r, c))) { + && GuiInfo.hideItemPanelSlot(gui, getSlotRect(r, c), handlers)) { invalidSlotMap[idx] = true; validColumn = false; perPage--; diff --git a/src/main/java/codechicken/nei/LayoutManager.java b/src/main/java/codechicken/nei/LayoutManager.java index f5944bb2..2eb45230 100644 --- a/src/main/java/codechicken/nei/LayoutManager.java +++ b/src/main/java/codechicken/nei/LayoutManager.java @@ -43,6 +43,7 @@ import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.gui.inventory.GuiContainerCreative; import net.minecraft.client.renderer.InventoryEffectRenderer; +import net.minecraft.client.renderer.Tessellator; import net.minecraft.inventory.Slot; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -907,21 +908,37 @@ public class LayoutManager implements IContainerInputHandler, IContainerTooltipH GL11.glEnable(GL11.GL_TEXTURE_2D); changeTexture("textures/gui/widgets.png"); - drawTexturedModalRect(x, y, tx1, ty1, w1, h1); // top left - drawTexturedModalRect(x, y2, tx1, ty2, w1, h2); // bottom left + final Tessellator tessellator = Tessellator.instance; + tessellator.startDrawingQuads(); + addTexturedModalRect(tessellator, x, y, tx1, ty1, w1, h1); // top left + addTexturedModalRect(tessellator, x, y2, tx1, ty2, w1, h2); // bottom left for (int tile = 0; tile < wTiles; tile++) { final int tileX = x + w1 + 50 * tile; - drawTexturedModalRect(tileX, y, tx3, ty1, 50, h1); // top - drawTexturedModalRect(tileX, y2, tx3, ty2, 50, h2); // bottom + addTexturedModalRect(tessellator, tileX, y, tx3, ty1, 50, h1); // top + addTexturedModalRect(tessellator, tileX, y2, tx3, ty2, 50, h2); // bottom } - drawTexturedModalRect(x2, y, tx2, ty1, w2, h1); // top right - drawTexturedModalRect(x2, y2, tx2, ty2, w2, h2); // bottom right + addTexturedModalRect(tessellator, x2, y, tx2, ty1, w2, h1); // top right + addTexturedModalRect(tessellator, x2, y2, tx2, ty2, w2, h2); // bottom right + tessellator.draw(); if (!is2DTexture) GL11.glDisable(GL11.GL_TEXTURE_2D); } + private static void addTexturedModalRect(Tessellator tessellator, int x, int y, int textureX, int textureY, + int width, int height) { + final double z = GuiDraw.gui.getZLevel(); + final double u = textureX / 256D; + final double v = textureY / 256D; + final double maxU = (textureX + width) / 256D; + final double maxV = (textureY + height) / 256D; + tessellator.addVertexWithUV(x, y + height, z, u, maxV); + tessellator.addVertexWithUV(x + width, y + height, z, maxU, maxV); + tessellator.addVertexWithUV(x + width, y, z, maxU, v); + tessellator.addVertexWithUV(x, y, z, u, v); + } + public static void drawItemPresenceOverlay(int slotX, int slotY, boolean isPresent, boolean slotHighlight) { if (slotHighlight) { diff --git a/src/main/java/codechicken/nei/NEIPotionGuiHandler.java b/src/main/java/codechicken/nei/NEIPotionGuiHandler.java index bbce8874..cd7ac6e7 100644 --- a/src/main/java/codechicken/nei/NEIPotionGuiHandler.java +++ b/src/main/java/codechicken/nei/NEIPotionGuiHandler.java @@ -1,14 +1,10 @@ package codechicken.nei; -import java.util.Collection; - import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.renderer.InventoryEffectRenderer; -import net.minecraft.potion.PotionEffect; -import codechicken.lib.vec.Rectangle4i; import codechicken.nei.api.INEIGuiAdapter; /** @@ -18,35 +14,37 @@ public class NEIPotionGuiHandler extends INEIGuiAdapter { @Override public boolean hideItemPanelSlot(GuiContainer guiContainer, int slotX, int slotY, int slotW, int slotH) { - if (NEIClientConfig.ignorePotionOverlap()) { + if (!(guiContainer instanceof InventoryEffectRenderer)) { return false; } - if (guiContainer instanceof InventoryEffectRenderer) { - int x = guiContainer.guiLeft - 124; - int y = guiContainer.guiTop; - Minecraft minecraft = guiContainer.mc; - if (minecraft == null) { - return false; - } - EntityPlayerSP player = minecraft.thePlayer; - if (player == null) { - return false; - } - Collection activePotionEffects = player.getActivePotionEffects(); - if (activePotionEffects.isEmpty()) { - return false; - } - int height = 33; - if (activePotionEffects.size() > 5) { - height = 132 / (activePotionEffects.size() - 1); - } - Rectangle4i slotRect = new Rectangle4i(slotX, slotY, slotW, slotH); - for (PotionEffect potioneffect : activePotionEffects) { - Rectangle4i box = new Rectangle4i(x, y, 140, 32); - if (box.intersects(slotRect)) return true; - y += height; - } + int x = guiContainer.guiLeft - 124; + if (slotX + slotW <= x || slotX >= x + 140) { + return false; + } + + Minecraft minecraft = guiContainer.mc; + if (minecraft == null) { + return false; + } + EntityPlayerSP player = minecraft.thePlayer; + if (player == null) { + return false; + } + int potionCount = player.getActivePotionEffects().size(); + if (potionCount == 0 || NEIClientConfig.ignorePotionOverlap()) { + return false; + } + int height = 33; + if (potionCount > 5) { + height = 132 / (potionCount - 1); + } + return intersectsPotionEffects(slotY, slotH, guiContainer.guiTop, potionCount, height); + } + + static boolean intersectsPotionEffects(int slotY, int slotH, int effectY, int potionCount, int height) { + for (int i = 0; i < potionCount; i++, effectY += height) { + if (slotY + slotH > effectY && slotY < effectY + 32) return true; } return false; } diff --git a/src/main/java/codechicken/nei/api/GuiInfo.java b/src/main/java/codechicken/nei/api/GuiInfo.java index 35415a02..40f9e24f 100644 --- a/src/main/java/codechicken/nei/api/GuiInfo.java +++ b/src/main/java/codechicken/nei/api/GuiInfo.java @@ -51,20 +51,29 @@ public class GuiInfo { } public static boolean hideItemPanelSlot(GuiContainer gui, Rectangle4i rect) { + return hideItemPanelSlot(gui, rect, getGuiHandlersSnapshot()); + } + + public static INEIGuiHandler[] getGuiHandlersSnapshot() { try { readLock.lock(); - if (guiHandlers.stream() - .anyMatch(handler -> handler.hideItemPanelSlot(gui, rect.x, rect.y, rect.w, rect.h))) { - return true; - } + return guiHandlers.toArray(new INEIGuiHandler[0]); } finally { readLock.unlock(); } + } + + public static boolean hideItemPanelSlot(GuiContainer gui, Rectangle4i rect, INEIGuiHandler[] handlers) { + for (INEIGuiHandler handler : handlers) { + if (handler.hideItemPanelSlot(gui, rect.x, rect.y, rect.w, rect.h)) return true; + } for (Object item : gui.buttonList) { GuiButton button = (GuiButton) item; - if ((new Rectangle4i(button.xPosition, button.yPosition, button.width, button.height)).intersects(rect)) { + if (button.xPosition + button.width > rect.x && button.xPosition < rect.x + rect.w + && button.yPosition + button.height > rect.y + && button.yPosition < rect.y + rect.h) { return true; } } diff --git a/src/main/java/codechicken/nei/guihook/GuiContainerManager.java b/src/main/java/codechicken/nei/guihook/GuiContainerManager.java index 1a5fb953..4a40f523 100644 --- a/src/main/java/codechicken/nei/guihook/GuiContainerManager.java +++ b/src/main/java/codechicken/nei/guihook/GuiContainerManager.java @@ -314,7 +314,7 @@ public class GuiContainerManager { } private static int modelviewDepth = -1; - private static boolean contextEnabled = false; + private static int contextDepth = 0; private static final ItemStackSet renderingErrorItems = new ItemStackSet(); public static void drawItem(int offsetX, int offsetY, ItemStack itemstack, FontRenderer fontRenderer, @@ -411,16 +411,15 @@ public class GuiContainerManager { } public static void enableMatrixStackLogging() { - if (!contextEnabled) { + if (contextDepth == 0) { GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT); modelviewDepth = GL11.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH); - contextEnabled = true; } + contextDepth++; } public static void disableMatrixStackLogging() { - if (contextEnabled) { - contextEnabled = false; + if (contextDepth > 0 && --contextDepth == 0) { modelviewDepth = -1; GL11.glPopAttrib(); } diff --git a/src/test/java/codechicken/nei/ItemsGridTest.java b/src/test/java/codechicken/nei/ItemsGridTest.java new file mode 100644 index 00000000..ec1153bc --- /dev/null +++ b/src/test/java/codechicken/nei/ItemsGridTest.java @@ -0,0 +1,58 @@ +package codechicken.nei; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; + +import net.minecraft.client.gui.inventory.GuiContainer; + +import org.junit.jupiter.api.Test; + +public class ItemsGridTest { + + @Test + void synchronizesOverlapAndCaptureRefresh() { + TestGrid grid = new TestGrid(); + ItemsGrid.ScreenCapture capture = mock(ItemsGrid.ScreenCapture.class); + grid.screenCapture = capture; + + grid.setGridSize(1, 2, 18, 18); + grid.setGridSize(1, 2, 18, 18); + verify(capture, times(1)).refreshBuffer(); + + when(capture.needRefresh(1)).thenReturn(false, true); + grid.refresh(null); + grid.refresh(null); + assertEquals(1, grid.overlapRefreshes); + } + + private static final class TestGrid extends ItemsGrid { + + private int overlapRefreshes; + + @Override + protected int getGridRenderingCacheMode() { + return 1; + } + + @Override + protected void updateGuiOverlapSlots(GuiContainer gui) { + overlapRefreshes++; + } + + @Override + public List getMask() { + return Collections.emptyList(); + } + + @Override + protected ItemsGrid.MouseContext getMouseContext(int mousex, int mousey) { + return null; + } + } +} diff --git a/src/test/java/codechicken/nei/NEIPotionGuiHandlerTest.java b/src/test/java/codechicken/nei/NEIPotionGuiHandlerTest.java new file mode 100644 index 00000000..4d3b7576 --- /dev/null +++ b/src/test/java/codechicken/nei/NEIPotionGuiHandlerTest.java @@ -0,0 +1,26 @@ +package codechicken.nei; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import net.minecraft.client.renderer.InventoryEffectRenderer; + +import org.junit.jupiter.api.Test; + +public class NEIPotionGuiHandlerTest { + + @Test + void preservesGapsBetweenPotionBoxes() { + assertTrue(NEIPotionGuiHandler.intersectsPotionEffects(131, 1, 100, 2, 33)); + assertFalse(NEIPotionGuiHandler.intersectsPotionEffects(132, 1, 100, 2, 33)); + } + + @Test + void skipsConfigLookupOutsidePotionColumn() { + InventoryEffectRenderer gui = mock(InventoryEffectRenderer.class); + gui.guiLeft = 200; + + assertFalse(new NEIPotionGuiHandler().hideItemPanelSlot(gui, 216, 100, 1, 1)); + } +} diff --git a/src/test/java/codechicken/nei/api/GuiInfoTest.java b/src/test/java/codechicken/nei/api/GuiInfoTest.java new file mode 100644 index 00000000..a088ace3 --- /dev/null +++ b/src/test/java/codechicken/nei/api/GuiInfoTest.java @@ -0,0 +1,36 @@ +package codechicken.nei.api; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.inventory.GuiContainer; + +import org.junit.jupiter.api.Test; + +import codechicken.lib.vec.Rectangle4i; + +public class GuiInfoTest { + + @Test + void checksProvidedHandlerSnapshot() { + GuiContainer gui = mock(GuiContainer.class); + INEIGuiHandler handler = mock(INEIGuiHandler.class); + when(handler.hideItemPanelSlot(gui, 1, 2, 3, 4)).thenReturn(true); + + assertTrue(GuiInfo.hideItemPanelSlot(gui, new Rectangle4i(1, 2, 3, 4), new INEIGuiHandler[] { handler })); + } + + @Test + void buttonEdgeContactDoesNotOverlap() { + GuiContainer gui = mock(GuiContainer.class); + gui.buttonList = Collections.singletonList(new GuiButton(0, 10, 10, 10, 10, "")); + + assertTrue(GuiInfo.hideItemPanelSlot(gui, new Rectangle4i(19, 19, 1, 1), new INEIGuiHandler[0])); + assertFalse(GuiInfo.hideItemPanelSlot(gui, new Rectangle4i(20, 10, 1, 1), new INEIGuiHandler[0])); + } +} diff --git a/src/test/java/codechicken/nei/guihook/GuiContainerManagerTest.java b/src/test/java/codechicken/nei/guihook/GuiContainerManagerTest.java new file mode 100644 index 00000000..d04754cd --- /dev/null +++ b/src/test/java/codechicken/nei/guihook/GuiContainerManagerTest.java @@ -0,0 +1,26 @@ +package codechicken.nei.guihook; + +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; + +import org.junit.jupiter.api.Test; +import org.lwjgl.opengl.GL11; +import org.mockito.MockedStatic; + +public class GuiContainerManagerTest { + + @Test + void matrixStackLoggingSupportsNestedContexts() { + try (MockedStatic gl = mockStatic(GL11.class)) { + GuiContainerManager.enableMatrixStackLogging(); + GuiContainerManager.enableMatrixStackLogging(); + GuiContainerManager.disableMatrixStackLogging(); + + gl.verify(() -> GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT)); + gl.verify(GL11::glPopAttrib, times(0)); + + GuiContainerManager.disableMatrixStackLogging(); + gl.verify(GL11::glPopAttrib); + } + } +}