diff --git a/src/main/java/top/fpsmaster/features/impl/interfaces/TargetDisplay.java b/src/main/java/top/fpsmaster/features/impl/interfaces/TargetDisplay.java index 1a9a4362..1d08ac13 100644 --- a/src/main/java/top/fpsmaster/features/impl/interfaces/TargetDisplay.java +++ b/src/main/java/top/fpsmaster/features/impl/interfaces/TargetDisplay.java @@ -100,7 +100,10 @@ private void drawCircle(Entity entity, double rad, boolean shade) { GL11.glEnd(); } finally { if (shade) { - GL11.glShadeModel(GL11.GL_FLAT); + // 还原成 SMOOTH,不是 FLAT:原版 GUI/世界渲染从不碰 shade model, + // 基线一直是 GL 默认的 GL_SMOOTH。这里留 FLAT,之后所有逐顶点着色的 + // 渐变(ClickGUI 调色板的 SV 方块、色相条、透明度条)都会被拍成一块纯色。 + GL11.glShadeModel(GL11.GL_SMOOTH); } GlStateManager.depthMask(true); GlStateManager.enableLighting(); diff --git a/src/main/java/top/fpsmaster/ui/kit/EdgeUi.java b/src/main/java/top/fpsmaster/ui/kit/EdgeUi.java index 2cfcdd68..8cdb0cde 100644 --- a/src/main/java/top/fpsmaster/ui/kit/EdgeUi.java +++ b/src/main/java/top/fpsmaster/ui/kit/EdgeUi.java @@ -13,6 +13,12 @@ public final class EdgeUi { private static UiFrame current; private static final FrameInput fallback = new FrameInput(); + /** + * 这一帧是不是宿主屏幕开的。键鼠缓冲只有喂过 {@link #keyTyped} 的那一帧有权清: + * HUD 覆盖层每帧都 begin/end 一次、又排在 drawScreen 前面,无脑清会把玩家这一帧 + * 敲的键冲掉——绑定按钮和文本框双双点不动,纯鼠标的开关滑条却照常。 + */ + private static boolean screenFrame; private EdgeUi() { } @@ -26,14 +32,19 @@ public static void begin(ScaledGuiScreen screen) { float w = screen == null ? 0f : screen.guiWidth; float h = screen == null ? 0f : screen.guiHeight; current = new UiFrame(new EdgeHost(screen, fallback, w, h), theme()); + screenFrame = true; } public static void beginOverlay(float guiWidth, float guiHeight) { current = new UiFrame(new EdgeHost(null, fallback, guiWidth, guiHeight), theme()); + screenFrame = false; } public static void end() { - fallback.endFrame(); + if (screenFrame) { + fallback.endFrame(); + } + screenFrame = false; EdgeCanvas.clearPanelClip(); current = null; } diff --git a/src/main/java/top/fpsmaster/ui/screens/cosmetics/CosmeticsScreen.java b/src/main/java/top/fpsmaster/ui/screens/cosmetics/CosmeticsScreen.java index cbffb98d..31edb1a5 100644 --- a/src/main/java/top/fpsmaster/ui/screens/cosmetics/CosmeticsScreen.java +++ b/src/main/java/top/fpsmaster/ui/screens/cosmetics/CosmeticsScreen.java @@ -8,7 +8,9 @@ import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.ResourceLocation; +import org.lwjgl.BufferUtils; import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.GL11; import top.fpsmaster.FPSMaster; import top.fpsmaster.exception.FileException; import top.fpsmaster.cosmetic.CosmeticManager; @@ -28,6 +30,7 @@ import com.google.gson.JsonObject; import java.io.IOException; +import java.nio.IntBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -40,6 +43,10 @@ public final class CosmeticsScreen extends ScaledGuiScreen { private final EdgeCosmeticsBridge bridge = new EdgeCosmeticsBridge(); private final float[] preview = new float[5]; private final List itemPreviews = new ArrayList<>(); + /** 商品列表的 GL 裁剪框,缩略图 2D 之后才画,得在回调那一刻抄下来照着裁。 */ + private final IntBuffer previewScissor = BufferUtils.createIntBuffer(16); + private boolean previewScissorCaptured; + private boolean previewScissorEnabled; public CosmeticsScreen(GuiScreen parent) { this.parent = parent; @@ -60,6 +67,8 @@ public void initGui() { @Override public void render(int mouseX, int mouseY, float partialTicks) { itemPreviews.clear(); + previewScissorCaptured = false; + previewScissorEnabled = false; if (cosmetics.draw(EdgeUi.frame(), bridge)) { mc.displayGuiScreen(parent); return; @@ -81,25 +90,53 @@ public void render(int mouseX, int mouseY, float partialTicks) { } private void renderItemPreviews() { - for (ItemPreview itemPreview : itemPreviews) { - ResourceLocation texture = bridge.cosmetics.textureFor(itemPreview.item.id()); - if ("wings".equals(itemPreview.item.category())) { - if (!itemPreview.item.builtin() && texture == null) continue; - float size = Math.max(12f, itemPreview.h * 0.4f); - bridge.cosmetics.wingsRenderer().renderPreview( - itemPreview.x + itemPreview.w * 0.5f, - itemPreview.y + itemPreview.h - 1f, - size, - 180f, - texture, - 0.78f - ); - } else if ("cape".equals(itemPreview.item.category()) && texture != null) { - renderCapeThumbnail(itemPreview, texture); + if (itemPreviews.isEmpty()) return; + // 缩略图是攒到 2D 通道结束之后才上屏的,那时 clip 栈已经弹空:不照着商品列表 + // 当时那道裁剪框自己裁一刀,往下滚的时候缩略图会横穿列表边界糊到面板外面去。 + boolean scissored = previewScissorEnabled; + if (scissored) { + GL11.glEnable(GL11.GL_SCISSOR_TEST); + GL11.glScissor(previewScissor.get(0), previewScissor.get(1), + previewScissor.get(2), previewScissor.get(3)); + } + try { + for (ItemPreview itemPreview : itemPreviews) { + ResourceLocation texture = bridge.cosmetics.textureFor(itemPreview.item.id()); + if ("wings".equals(itemPreview.item.category())) { + if (!itemPreview.item.builtin() && texture == null) continue; + float size = Math.max(12f, itemPreview.h * 0.4f); + bridge.cosmetics.wingsRenderer().renderPreview( + itemPreview.x + itemPreview.w * 0.5f, + itemPreview.y + itemPreview.h - 1f, + size, + 180f, + texture, + 0.78f + ); + } else if ("cape".equals(itemPreview.item.category()) && texture != null) { + renderCapeThumbnail(itemPreview, texture); + } + } + } finally { + if (scissored) { + GL11.glDisable(GL11.GL_SCISSOR_TEST); } } } + /** + * 抄一份当前 GL 裁剪框。商品列表是同一道裁剪,一帧抄一次就够。 + */ + private void capturePreviewScissor() { + if (previewScissorCaptured) return; + previewScissorCaptured = true; + previewScissorEnabled = GL11.glIsEnabled(GL11.GL_SCISSOR_TEST); + if (previewScissorEnabled) { + previewScissor.clear(); + GL11.glGetInteger(GL11.GL_SCISSOR_BOX, previewScissor); + } + } + private void renderCapeThumbnail(ItemPreview itemPreview, ResourceLocation texture) { float size = Math.max(18f, itemPreview.h * 0.76f); GlStateManager.enableColorMaterial(); @@ -323,6 +360,7 @@ public void purchaseItem(String id) { public void paintItemPreview(UiFrame ui, CosmeticsBridge.Item item, float x, float y, float w, float h) { itemPreviews.add(new ItemPreview(item, x, y, w, h)); + capturePreviewScissor(); } @Override public void paintPlayerPreview(UiFrame ui, float x, float y, float w, float h, float yaw) { diff --git a/src/main/java/top/fpsmaster/utils/render/gui/ScaledGuiScreen.java b/src/main/java/top/fpsmaster/utils/render/gui/ScaledGuiScreen.java index a4004e79..df077ea7 100644 --- a/src/main/java/top/fpsmaster/utils/render/gui/ScaledGuiScreen.java +++ b/src/main/java/top/fpsmaster/utils/render/gui/ScaledGuiScreen.java @@ -129,6 +129,12 @@ public void onGuiClosed() { @Override protected void keyTyped(char typedChar, int keyCode) throws IOException { + // handleInput 里先跑鼠标后跑键盘:本屏在 handleMouseInput 里关掉后,同一批 + // Keyboard.next() 的余下按键仍会派到这个死实例上。缓冲现在只由宿主屏幕帧清, + // 死屏喂进来的键会一直攒到下一个 ScaledGuiScreen 打开才冒出来,直接丢掉。 + if (mc.currentScreen != this) { + return; + } EdgeUi.keyTyped(typedChar, keyCode); } diff --git a/src/main/java/top/fpsmaster/utils/render/shader/GradientUtils.java b/src/main/java/top/fpsmaster/utils/render/shader/GradientUtils.java index 77625dfe..95ef195f 100644 --- a/src/main/java/top/fpsmaster/utils/render/shader/GradientUtils.java +++ b/src/main/java/top/fpsmaster/utils/render/shader/GradientUtils.java @@ -82,7 +82,9 @@ public static void drawGradientVertical(float x, float y, float width, float hei GL11.glVertex2f(x + width, y + height); GL11.glEnd(); - GL11.glShadeModel(7424); + // 回到 GL 默认的 SMOOTH(原版 GUI 从不设置 shade model,基线就是它)。 + // 留 FLAT 会把之后所有逐顶点着色的渐变——比如 ClickGUI 调色板——拍成一块纯色。 + GL11.glShadeModel(7425); GL11.glEnable(3553); GL11.glDisable(3042); GL11.glEnable(2929);