1111import java .util .UUID ;
1212import java .util .ArrayList ;
1313import java .util .Optional ;
14+ import java .util .Locale ;
1415import java .util .concurrent .CompletableFuture ;
1516import java .util .concurrent .ConcurrentHashMap ;
1617import java .util .concurrent .ConcurrentMap ;
1920import net .minecraft .client .Minecraft ;
2021import net .minecraft .client .gui .GuiGraphicsExtractor ;
2122import net .minecraft .client .gui .components .Button ;
23+ import net .minecraft .client .gui .components .EditBox ;
2224import net .minecraft .client .gui .screens .Screen ;
2325import net .minecraft .client .input .KeyEvent ;
2426import net .minecraft .client .input .MouseButtonEvent ;
@@ -45,11 +47,13 @@ public final class OppStatsScreen extends Screen
4547 private final Screen previous ;
4648 private final OppStatsHack hack ;
4749 private OppList list ;
50+ private EditBox searchBox ;
4851 private Button onlineButton ;
4952 private Button historicalButton ;
5053 private Button copyButton ;
5154 private Button copyEventsButton ;
5255 private boolean showOnline = true ;
56+ private String searchQuery = "" ;
5357 private long nextReloadAt ;
5458 private int infoScroll ;
5559 private int infoMaxScroll ;
@@ -68,25 +72,37 @@ public OppStatsScreen(Screen previous, OppStatsHack hack)
6872 @ Override
6973 protected void init ()
7074 {
71- int top = 40 ;
75+ int top = 68 ;
7276 int bottomPad = 44 ;
7377 int listHeight = height - top - bottomPad ;
78+ int leftPanelX = 16 ;
79+ int leftPanelW = Math .min (440 , Math .max (220 , width / 2 - 36 ));
7480 list = new OppList (Minecraft .getInstance (), width / 2 - 20 , listHeight ,
75- top , 24 , hack , showOnline );
81+ top , 24 , hack , showOnline , searchQuery );
7682 addWidget (list );
7783
7884 addRenderableWidget (
7985 onlineButton = Button .builder (Component .literal ("Online" ), b -> {
8086 showOnline = true ;
81- list .reload (showOnline );
87+ list .reload (showOnline , searchQuery );
8288 updateModeButtonLabels ();
8389 }).bounds (16 , 12 , 100 , 20 ).build ());
8490 addRenderableWidget (historicalButton =
8591 Button .builder (Component .literal ("Historical" ), b -> {
8692 showOnline = false ;
87- list .reload (showOnline );
93+ list .reload (showOnline , searchQuery );
8894 updateModeButtonLabels ();
8995 }).bounds (122 , 12 , 110 , 20 ).build ());
96+ searchBox = new EditBox (font , leftPanelX , 36 , leftPanelW , 18 ,
97+ Component .literal ("Search" ));
98+ searchBox .setHint (Component .literal ("Search players..." ));
99+ searchBox .setValue (searchQuery );
100+ searchBox .setResponder (value -> {
101+ searchQuery = value == null ? "" : value .trim ();
102+ if (list != null )
103+ list .reload (showOnline , searchQuery );
104+ });
105+ addRenderableWidget (searchBox );
90106 copyButton = addRenderableWidget (Button
91107 .builder (Component .literal ("Copy Profile" ), b -> copySelected ())
92108 .bounds (width - 360 , height - 32 , 110 , 20 ).build ());
@@ -105,7 +121,7 @@ public void tick()
105121 super .tick ();
106122 if (System .currentTimeMillis () >= nextReloadAt )
107123 {
108- list .reload (showOnline );
124+ list .reload (showOnline , searchQuery );
109125 nextReloadAt = System .currentTimeMillis () + 700L ;
110126 }
111127 updateModeButtonLabels ();
@@ -169,10 +185,13 @@ public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick)
169185 @ Override
170186 public boolean keyPressed (KeyEvent context )
171187 {
172- if (context .key () == GLFW .GLFW_KEY_UP )
173- return list != null && list .moveSelection (-1 );
174- if (context .key () == GLFW .GLFW_KEY_DOWN )
175- return list != null && list .moveSelection (1 );
188+ if (searchBox == null || !searchBox .isFocused ())
189+ {
190+ if (context .key () == GLFW .GLFW_KEY_UP )
191+ return list != null && list .moveSelection (-1 );
192+ if (context .key () == GLFW .GLFW_KEY_DOWN )
193+ return list != null && list .moveSelection (1 );
194+ }
176195
177196 if (super .keyPressed (context ))
178197 return true ;
@@ -326,7 +345,7 @@ private void drawInfoPanel(GuiGraphicsExtractor context, OppRecord selected,
326345 lines .add (new InfoLine ("Status" , true ));
327346 lines .add (new InfoLine ("HP: " + fmt (selected .health ) + " Abs: "
328347 + fmt (selected .absorption ) + " Armor: " + na (selected .armorValue )
329- + " Ping: " + na (selected . ping ), false ));
348+ + " Ping: " + na (getLivePing ( selected ) ), false ));
330349 lines .add (new InfoLine ("Gamemode: " + na (selected .gamemode ), false ));
331350 lines .add (new InfoLine ("" , false ));
332351 lines .add (new InfoLine ("Stats" , true ));
@@ -530,7 +549,7 @@ private String fmt(float v)
530549 return String .format ("%.1f" , v );
531550 }
532551
533- private String na (int value )
552+ private static String na (int value )
534553 {
535554 return value < 0 ? "N/A" : String .valueOf (value );
536555 }
@@ -540,6 +559,19 @@ private String na(String value)
540559 return value == null || value .isBlank () ? "N/A" : value ;
541560 }
542561
562+ private static int getLivePing (OppRecord rec )
563+ {
564+ Minecraft mc = Minecraft .getInstance ();
565+ if (rec == null || rec .uuid == null || mc .getConnection () == null )
566+ return rec == null ? -1 : rec .ping ;
567+
568+ PlayerInfo info = mc .getConnection ().getPlayerInfo (rec .uuid );
569+ if (info != null )
570+ return info .getLatency ();
571+
572+ return rec .ping ;
573+ }
574+
543575 private Identifier resolveSkin (UUID uuid )
544576 {
545577 requestMojangSkin (uuid );
@@ -601,24 +633,31 @@ private static final class OppList
601633 private boolean showOnline ;
602634
603635 public OppList (Minecraft mc , int width , int height , int top ,
604- int itemHeight , OppStatsHack hack , boolean showOnline )
636+ int itemHeight , OppStatsHack hack , boolean showOnline ,
637+ String searchQuery )
605638 {
606639 super (mc , width , height , top , itemHeight );
607640 this .hack = hack ;
608641 this .showOnline = showOnline ;
609- reload (showOnline );
642+ reload (showOnline , searchQuery );
610643 ensureSelection ();
611644 }
612645
613- void reload (boolean showOnline )
646+ void reload (boolean showOnline , String searchQuery )
614647 {
615648 this .showOnline = showOnline ;
616649 var prev = captureState ();
617650 clearEntries ();
618651 List <OppRecord > src = showOnline ? hack .getOnlineRecords ()
619652 : hack .getHistoricalRecords ();
653+ String query = searchQuery == null ? ""
654+ : searchQuery .trim ().toLowerCase (Locale .ROOT );
620655 for (OppRecord rec : src )
656+ {
657+ if (!query .isBlank () && !matchesSearch (rec , query ))
658+ continue ;
621659 addEntry (new Entry (this , rec ));
660+ }
622661 if (!children ().isEmpty ())
623662 restoreState (prev );
624663 }
@@ -669,6 +708,21 @@ public int getRowWidth()
669708 return width - 20 ;
670709 }
671710
711+ private boolean matchesSearch (OppRecord rec , String query )
712+ {
713+ if (rec == null || query == null || query .isBlank ())
714+ return true ;
715+
716+ String name =
717+ rec .name == null ? "" : rec .name .toLowerCase (Locale .ROOT );
718+ if (name .contains (query ))
719+ return true ;
720+
721+ String uuid = rec .uuid == null ? ""
722+ : rec .uuid .toString ().toLowerCase (Locale .ROOT );
723+ return uuid .contains (query );
724+ }
725+
672726 private static final class Entry
673727 extends MultiSelectEntryListWidget .Entry <Entry >
674728 {
@@ -705,9 +759,10 @@ public void extractContent(GuiGraphicsExtractor context, int mouseX,
705759 40 , 8 , 16 , 16 , 8 , 8 , 64 , 64 , 0xFFFFFFFF );
706760 context .text (Minecraft .getInstance ().font , record .name , x + 24 ,
707761 y + 2 , 0xFFFFFFFF , false );
708- context .text (Minecraft .getInstance ().font ,
709- record .online ? "online" : "offline" , x + 24 , y + 12 ,
710- record .online ? 0xFF55FF55 : 0xFFFF7777 , false );
762+ String pingText = record .online
763+ ? "ping: " + na (getLivePing (record )) + " ms" : "offline" ;
764+ context .text (Minecraft .getInstance ().font , pingText , x + 24 ,
765+ y + 12 , record .online ? 0xFF55FF55 : 0xFFFF7777 , false );
711766 }
712767
713768 private Identifier resolveSkin (UUID uuid )
0 commit comments