3333import brachy .modularui .widgets .*;
3434import brachy .modularui .widgets .layout .Flow ;
3535import com .google .common .base .Strings ;
36+ import it .unimi .dsi .fastutil .objects .Object2ObjectOpenHashMap ;
3637import lombok .Getter ;
3738import org .jetbrains .annotations .NotNull ;
3839import org .jetbrains .annotations .Nullable ;
@@ -65,8 +66,10 @@ public class ProspectorMapHandler<T> extends Widget<ProspectorMapHandler<T>> imp
6566 // runtime
6667 @ Getter
6768 private @ Nullable String selected = null ;
68- private final Set <T > items = new HashSet <>();
69+ // keyed by uniqueId so entries are deduplicated across chunks
70+ private final Map <String , T > items = new Object2ObjectOpenHashMap <>();
6971 private int chunkIndex = 0 ;
72+ private String lastSearch = "" ;
7073
7174 public ProspectorMapHandler (ProspectorMode <T > mode , int chunkRadius , int scanInterval ,
7275 StringValue searchValue , DynamicSyncedWidget <?> searchListWidget ,
@@ -87,6 +90,16 @@ public ProspectorMapHandler(ProspectorMode<T> mode, int chunkRadius, int scanInt
8790 this .texture = new ProspectorMapTexture <>(this );
8891 background (this .texture );
8992 size (this .texture .getImageWidth (), this .texture .getImageHeight ());
93+
94+ tooltipAutoUpdate (true );
95+ tooltipDynamic (tooltip -> {
96+ tooltip .clearText ();
97+ List <T []> cells = getHoveredChunkCells ();
98+ if (cells .isEmpty ()) return ;
99+ List <Component > lines = new ArrayList <>();
100+ this .mode .appendTooltips (cells , lines , null );
101+ lines .forEach (tooltip ::addLine );
102+ });
90103 } else {
91104 int diameter = (chunkRadius * 2 - 1 ) * 16 + 1 ;
92105 size (diameter , diameter );
@@ -108,7 +121,14 @@ private DynamicSyncHandler createListSyncHandler() {
108121 .collapseDisabledChildren ()
109122 .expanded ()
110123 .sizeRel (1f )
111- .children (this .items , item -> {
124+ .onUpdateListener (list -> {
125+ String current = searchValue .getStringValue ();
126+ if (!Objects .equals (current , this .lastSearch )) {
127+ this .lastSearch = current ;
128+ list .getScrollData ().scrollTo (list .getScrollArea (), 0 );
129+ }
130+ })
131+ .children (this .items .values (), item -> {
112132 String uniqueId = mode .getUniqueId (item );
113133 Component description = mode .getDescription (item );
114134
@@ -126,7 +146,8 @@ private DynamicSyncHandler createListSyncHandler() {
126146 if (Strings .isNullOrEmpty (searched )) {
127147 return true ;
128148 } else {
129- return description .getString ().toLowerCase ().contains (searched );
149+ return description .getString ().toLowerCase ()
150+ .contains (searched .toLowerCase ());
130151 }
131152 })
132153 .child (Flow .row ()
@@ -178,7 +199,11 @@ private void scanOres() {
178199 private void addOresToList (T [][][] data ) {
179200 for (int x = 0 ; x < mode .cellSize ; x ++) {
180201 for (int z = 0 ; z < mode .cellSize ; z ++) {
181- Collections .addAll (this .items , data [x ][z ]);
202+ for (T item : data [x ][z ]) {
203+ if (item != null ) {
204+ this .items .putIfAbsent (mode .getUniqueId (item ), item );
205+ }
206+ }
182207 }
183208 }
184209 }
@@ -219,39 +244,70 @@ public void setDarkMode(boolean darkMode) {
219244 Component .translatable ("behavior.prospector.added_waypoint" ,
220245 clickedItem .name .copy ().withStyle (style -> style .withColor (clickedItem .color ))),
221246 false );
247+ this .getContext ().getScreen ().getMainPanel ().closeIfOpen ();
222248
223249 Interactable .playButtonClickSound ();
224250 return Result .SUCCESS ;
225251 }
226252
253+ /**
254+ * @return every non-empty data cell of the chunk currently under the cursor, or an empty list if the
255+ * cursor is off the map.
256+ */
257+ private List <T []> getHoveredChunkCells () {
258+ if (this .texture == null ) return List .of ();
259+ int relX = getContext ().getMouseX ();
260+ int relZ = getContext ().getMouseY ();
261+
262+ int mapPixels = (this .chunkRadius * 2 - 1 ) * 16 ;
263+ if (relX < 0 || relZ < 0 || relX >= mapPixels || relZ >= mapPixels ) {
264+ return List .of ();
265+ }
266+
267+ int chunkX = relX / 16 ;
268+ int chunkZ = relZ / 16 ;
269+ int cellSize = this .mode .cellSize ;
270+ List <T []> cells = new ArrayList <>();
271+ for (int i = 0 ; i < cellSize ; i ++) {
272+ for (int j = 0 ; j < cellSize ; j ++) {
273+ T [] cell = this .texture .data [chunkX * cellSize + i ][chunkZ * cellSize + j ];
274+ if (cell != null && cell .length > 0 ) {
275+ cells .add (cell );
276+ }
277+ }
278+ }
279+ return cells ;
280+ }
281+
227282 private @ Nullable WaypointItem getClickedVein (double mouseX , double mouseY ) {
228283 if (this .texture == null ) return null ;
229- int chunkX = (int ) (mouseX - getArea ().x ()) / 16 ;
230- int chunkZ = (int ) (mouseY - getArea ().y ()) / 16 ;
231- int offsetX = (int ) (mouseX - getArea ().x ()) % 16 ;
232- int offsetZ = (int ) (mouseY - getArea ().y ()) % 16 ;
284+ int relX = (int ) mouseX ;
285+ int relZ = (int ) mouseY ;
286+
287+ int mapPixels = (this .chunkRadius * 2 - 1 ) * 16 ;
288+ if (relX < 0 || relZ < 0 || relX >= mapPixels || relZ >= mapPixels ) {
289+ return null ;
290+ }
291+
292+ int chunkX = relX / 16 ;
293+ int chunkZ = relZ / 16 ;
294+ int offsetX = relX % 16 ;
295+ int offsetZ = relZ % 16 ;
233296 int xDiff = chunkX - (this .chunkRadius - 1 );
234297 int zDiff = chunkZ - (this .chunkRadius - 1 );
235298
236299 int x = SectionPos .sectionToBlockCoord (player .chunkPosition ().x + xDiff ) + offsetX ;
237300 int z = SectionPos .sectionToBlockCoord (player .chunkPosition ().z + zDiff ) + offsetZ ;
238301 int y = player .level ().getHeight (Heightmap .Types .WORLD_SURFACE , x , z );
239302
240- if (chunkX < 0 || chunkZ < 0 || chunkX >= this .chunkRadius * 2 - 1 || chunkZ >= this .chunkRadius * 2 - 1 ) {
241- return null ;
242- }
243-
244303 BlockPos pos = new BlockPos (x , y , z );
245304 // If the ores are filtered use its name
246- if (this .getSelected () != null ) {
247- for (T item : this .items ) {
248- String uniqueId = mode .getUniqueId (item );
249- if (!this .getSelected ().equals (uniqueId )) continue ;
250-
251- Component name = mode .getDescription (item );
252- int color = mode .getItemColor (item );
253- return new WaypointItem (pos , uniqueId , name , color );
254- }
305+ T item = this .items .get (this .getSelected ());
306+ if (item != null ) {
307+ Component name = mode .getDescription (item );
308+ int color = mode .getItemColor (item );
309+ String uniqueId = mode .getUniqueId (item );
310+ return new WaypointItem (pos , uniqueId , name , color );
255311 }
256312
257313 // If the cursor is over an ore use its name
0 commit comments