@@ -90,6 +90,7 @@ public final class ClientMessageOverlay
9090 private int lastWurstY1 ;
9191 private int lastWurstX2 ;
9292 private int lastWurstY2 ;
93+ private boolean tabHeldForOverlay ;
9394
9495 private ClientMessageOverlay ()
9596 {}
@@ -228,24 +229,34 @@ public void render(GuiGraphicsExtractor context)
228229 if (hack == null || !hack .isEnabled ())
229230 return ;
230231
232+ handleHoldTabOpensChat (hack );
233+
231234 boolean splitWurstPanel = hack .isExtraPanelForWurstMessages ();
232- if (messages .isEmpty () && (!splitWurstPanel || wurstMessages .isEmpty ()))
235+ boolean hasVanillaMessages =
236+ tabHeldForOverlay && hasVanillaChatMessages ();
237+ if (messages .isEmpty () && (!splitWurstPanel || wurstMessages .isEmpty ())
238+ && !hasVanillaMessages )
233239 return ;
234240
235241 float chatScale = WurstClient .MC .options .chatScale ().get ().floatValue ();
236242 if (chatScale <= 0 )
237243 return ;
238244
239- boolean chatOpen = WurstClient .MC .screen instanceof ChatScreen ;
245+ boolean chatOpen =
246+ WurstClient .MC .screen instanceof ChatScreen || tabHeldForOverlay ;
240247 int maxLines = hack .getMaxLines ();
241248 int maxWidth = Mth .floor (
242249 ChatComponent .getWidth (WurstClient .MC .options .chatWidth ().get ())
243250 / chatScale );
244251 if (maxWidth <= 0 )
245252 return ;
246253
247- List <RenderLine > allLines =
248- buildAllLines (messages , maxWidth , chatOpen || hoveredMainPanel );
254+ List <RenderLine > allLines ;
255+ if (tabHeldForOverlay )
256+ allLines = buildCombinedLines (messages , maxWidth );
257+ else
258+ allLines =
259+ buildAllLines (messages , maxWidth , chatOpen || hoveredMainPanel );
249260 totalLineCount = allLines .size ();
250261 int maxScroll = Math .max (0 , totalLineCount - maxLines );
251262 scrollOffset = Mth .clamp (scrollOffset , 0 , maxScroll );
@@ -1013,6 +1024,62 @@ private static String extractLastUsernameToken(String text)
10131024 return last ;
10141025 }
10151026
1027+ private boolean hasVanillaChatMessages ()
1028+ {
1029+ List <GuiMessage .Line > vanillaLines =
1030+ WurstClient .MC .gui .getChat ().trimmedMessages ;
1031+ return vanillaLines != null && !vanillaLines .isEmpty ();
1032+ }
1033+
1034+ private List <RenderLine > buildCombinedLines (Deque <Entry > overlayMessages ,
1035+ int maxWidth )
1036+ {
1037+ List <TimedRenderLine > timed = new ArrayList <>();
1038+ long nowMs = System .currentTimeMillis ();
1039+ int guiTicks = WurstClient .MC .gui .getGuiTicks ();
1040+
1041+ // Overlay entries (system/Wurst messages)
1042+ synchronized (overlayMessages )
1043+ {
1044+ for (Entry entry : overlayMessages )
1045+ {
1046+ Component renderComponent =
1047+ applyDefaultTextColorIfEnabled (entry .component ());
1048+ List <FormattedCharSequence > wrapped =
1049+ ComponentRenderUtils .wrapComponents (renderComponent ,
1050+ maxWidth , WurstClient .MC .font );
1051+ for (int i = wrapped .size () - 1 ; i >= 0 ; i --)
1052+ timed .add (
1053+ new TimedRenderLine (new RenderLine (wrapped .get (i ), 255 ),
1054+ entry .timestamp ()));
1055+ }
1056+ }
1057+
1058+ // Vanilla chat entries (player chat messages)
1059+ List <GuiMessage .Line > vanillaLines =
1060+ WurstClient .MC .gui .getChat ().trimmedMessages ;
1061+ if (vanillaLines != null )
1062+ for (GuiMessage .Line line : vanillaLines )
1063+ {
1064+ int tickAge = guiTicks - line .addedTime ();
1065+ if (tickAge >= 0 )
1066+ timed .add (
1067+ new TimedRenderLine (new RenderLine (line .content (), 255 ),
1068+ nowMs - tickAge * 50L ));
1069+ }
1070+
1071+ // Sort newest first (matching buildAllLines order)
1072+ timed .sort ((a , b ) -> Long .compare (b .sortKey , a .sortKey ));
1073+
1074+ List <RenderLine > result = new ArrayList <>(timed .size ());
1075+ for (TimedRenderLine t : timed )
1076+ result .add (t .line );
1077+ return result ;
1078+ }
1079+
1080+ private record TimedRenderLine (RenderLine line , long sortKey )
1081+ {}
1082+
10161083 private List <RenderLine > buildAllLines (Deque <Entry > source , int maxWidth ,
10171084 boolean fullOpacity )
10181085 {
@@ -1379,6 +1446,32 @@ private static boolean isMouseOverPanel(GuiGraphicsExtractor context,
13791446 return mouseX >= x1 && mouseX <= x2 && mouseY >= y1 && mouseY <= y2 ;
13801447 }
13811448
1449+ private void handleHoldTabOpensChat (ClientChatOverlayHack hack )
1450+ {
1451+ if (WurstClient .MC == null )
1452+ return ;
1453+
1454+ if (!hack .shouldHoldTabOpenChat ())
1455+ {
1456+ tabHeldForOverlay = false ;
1457+ return ;
1458+ }
1459+
1460+ Window window = WurstClient .MC .getWindow ();
1461+ if (window == null )
1462+ return ;
1463+
1464+ boolean tabDown = GLFW .glfwGetKey (window .handle (),
1465+ GLFW .GLFW_KEY_TAB ) == GLFW .GLFW_PRESS ;
1466+
1467+ tabHeldForOverlay = tabDown ;
1468+ }
1469+
1470+ public boolean isTabHeldForOverlay ()
1471+ {
1472+ return tabHeldForOverlay ;
1473+ }
1474+
13821475 private record Entry (Component component , long timestamp )
13831476 {}
13841477
0 commit comments