1111import java .util .Collections ;
1212import java .util .List ;
1313import java .util .concurrent .CompletableFuture ;
14- import java .util .concurrent .ExecutorService ;
1514import java .util .function .BiPredicate ;
1615import java .util .stream .Stream ;
16+ import org .slf4j .Logger ;
17+ import com .mojang .logging .LogUtils ;
1718import net .minecraft .core .BlockPos ;
19+ import net .minecraft .core .SectionPos ;
1820import net .minecraft .world .level .ChunkPos ;
21+ import net .minecraft .world .level .block .Blocks ;
1922import net .minecraft .world .level .block .state .BlockState ;
2023import net .minecraft .world .level .chunk .ChunkAccess ;
24+ import net .minecraft .world .level .chunk .LevelChunkSection ;
25+ import net .minecraft .world .level .chunk .MissingPaletteEntryException ;
26+ import net .minecraft .world .level .chunk .PalettedContainer ;
2127import net .minecraft .world .level .dimension .DimensionType ;
28+ import net .wurstclient .WurstClient ;
2229import net .wurstclient .util .MinPriorityThreadFactory ;
2330
2431/**
2532 * Searches the given {@link ChunkAccess} for blocks matching the given query.
2633 */
2734public final class ChunkSearcher
2835{
29- private static final ExecutorService BACKGROUND_THREAD_POOL =
36+ private static final Logger LOGGER = LogUtils .getLogger ();
37+ private static final java .util .concurrent .ExecutorService BACKGROUND_THREAD_POOL =
3038 MinPriorityThreadFactory .newFixedThreadPool ();
3139
3240 private final BiPredicate <BlockPos , BlockState > query ;
@@ -51,21 +59,31 @@ public void start()
5159 if (future != null || interrupted )
5260 throw new IllegalStateException ();
5361
54- future = CompletableFuture .supplyAsync (this ::searchNow ,
62+ ChunkSnapshot snapshot = ChunkSnapshot .capture (chunk );
63+ if (snapshot == null )
64+ {
65+ future = CompletableFuture .completedFuture (new ArrayList <>());
66+ return ;
67+ }
68+
69+ future = CompletableFuture .supplyAsync (() -> searchNow (snapshot ),
5570 BACKGROUND_THREAD_POOL );
5671 }
5772
58- private ArrayList <Result > searchNow ()
73+ private ArrayList <Result > searchNow (ChunkSnapshot snapshot )
5974 {
6075 ArrayList <Result > results = new ArrayList <>();
61- ChunkPos chunkPos = chunk .getPos ();
76+ boolean reportedMissingEntry = false ;
77+ ChunkPos chunkPos = snapshot .chunkPos ();
6278
63- int minX = chunkPos .getMinBlockX ();
64- int minY = chunk .getMinBuildHeight ();
65- int minZ = chunkPos .getMinBlockZ ();
66- int maxX = chunkPos .getMaxBlockX ();
67- int maxY = ChunkUtils .getHighestNonEmptySectionYOffset (chunk ) + 16 ;
68- int maxZ = chunkPos .getMaxBlockZ ();
79+ int minX = snapshot .minX ();
80+ int minY = snapshot .minY ();
81+ int minZ = snapshot .minZ ();
82+ int maxX = snapshot .maxX ();
83+ int maxY = snapshot .maxY ();
84+ int maxZ = snapshot .maxZ ();
85+
86+ BlockPos .MutableBlockPos mutablePos = new BlockPos .MutableBlockPos ();
6987
7088 for (int x = minX ; x <= maxX ; x ++)
7189 for (int y = minY ; y <= maxY ; y ++)
@@ -74,12 +92,27 @@ private ArrayList<Result> searchNow()
7492 if (interrupted )
7593 return results ;
7694
77- BlockPos pos = new BlockPos (x , y , z );
78- BlockState state = chunk .getBlockState (pos );
79- if (!query .test (pos , state ))
95+ mutablePos .set (x , y , z );
96+ BlockState state ;
97+ try
98+ {
99+ state = snapshot .getBlockState (mutablePos );
100+
101+ }catch (MissingPaletteEntryException e )
102+ {
103+ if (!reportedMissingEntry )
104+ {
105+ reportedMissingEntry = true ;
106+ LOGGER .warn (
107+ "ChunkSearcher skipped palette gap in chunk {}: {}" ,
108+ chunkPos , e .getMessage ());
109+ }
110+ continue ;
111+ }
112+ if (!query .test (mutablePos , state ))
80113 continue ;
81114
82- results .add (new Result (pos .immutable (), state ));
115+ results .add (new Result (mutablePos .immutable (), state ));
83116 }
84117
85118 return results ;
@@ -115,6 +148,9 @@ public Stream<Result> getMatches()
115148 return Stream .empty ();
116149
117150 ensureResultsLoaded ();
151+ if (results == null )
152+ return Stream .empty ();
153+
118154 ArrayList <Result > snapshot ;
119155 synchronized (this )
120156 {
@@ -129,6 +165,9 @@ public List<Result> getMatchesList()
129165 return List .of ();
130166
131167 ensureResultsLoaded ();
168+ if (results == null )
169+ return List .of ();
170+
132171 synchronized (this )
133172 {
134173 return Collections .unmodifiableList (new ArrayList <>(results ));
@@ -208,6 +247,9 @@ private void ensureResultsLoaded()
208247 if (results != null || future == null || future .isCancelled ())
209248 return ;
210249
250+ if (!future .isDone ())
251+ return ;
252+
211253 ArrayList <Result > computed = future .join ();
212254
213255 synchronized (this )
@@ -276,4 +318,61 @@ public record Result(BlockPos pos, BlockState state)
276318
277319 public record BlockUpdate (BlockPos pos , BlockState state )
278320 {}
321+
322+ private record ChunkSnapshot (ChunkPos chunkPos , int minX , int minY ,
323+ int minZ , int maxX , int maxY , int maxZ , int minSectionCoord ,
324+ PalettedContainer <BlockState >[] sections )
325+ {
326+ static ChunkSnapshot capture (ChunkAccess chunk )
327+ {
328+ if (WurstClient .MC == null || WurstClient .MC .level == null )
329+ return null ;
330+
331+ ChunkPos chunkPos = chunk .getPos ();
332+ if (!WurstClient .MC .level .hasChunk (chunkPos .x , chunkPos .z ))
333+ return null ;
334+
335+ LevelChunkSection [] chunkSections = chunk .getSections ();
336+ @ SuppressWarnings ("unchecked" )
337+ PalettedContainer <BlockState >[] copies =
338+ new PalettedContainer [chunkSections .length ];
339+
340+ for (int i = 0 ; i < chunkSections .length ; i ++)
341+ {
342+ LevelChunkSection section = chunkSections [i ];
343+ if (section == null || section .hasOnlyAir ())
344+ continue ;
345+
346+ copies [i ] = section .getStates ().copy ();
347+ }
348+
349+ int minX = chunkPos .getMinBlockX ();
350+ int minY = chunk .getMinY ();
351+ int minZ = chunkPos .getMinBlockZ ();
352+ int maxX = chunkPos .getMaxBlockX ();
353+ int maxY = ChunkUtils .getHighestNonEmptySectionYOffset (chunk ) + 16 ;
354+ int maxZ = chunkPos .getMaxBlockZ ();
355+ int minSectionCoord = SectionPos .blockToSectionCoord (minY );
356+
357+ return new ChunkSnapshot (chunkPos , minX , minY , minZ , maxX , maxY ,
358+ maxZ , minSectionCoord , copies );
359+ }
360+
361+ BlockState getBlockState (BlockPos pos )
362+ {
363+ int ySection = SectionPos .blockToSectionCoord (pos .getY ());
364+ int sectionIndex = ySection - minSectionCoord ;
365+
366+ PalettedContainer <BlockState > container = null ;
367+
368+ if (sectionIndex >= 0 && sectionIndex < sections .length )
369+ container = sections [sectionIndex ];
370+
371+ if (container == null )
372+ return Blocks .AIR .defaultBlockState ();
373+
374+ return container .get (pos .getX () & 15 , pos .getY () & 15 ,
375+ pos .getZ () & 15 );
376+ }
377+ }
279378}
0 commit comments