@@ -205,6 +205,8 @@ public static double getHealth(ActorModel npc) {
205205 return (double ) ratio / (double ) scale * 100 ;
206206 }
207207
208+ private static final Rs2NpcModel [] EMPTY_ARRAY = new Rs2NpcModel [0 ];
209+
208210 /**
209211 * Retrieves a stream of NPCs filtered by a given condition.
210212 *
@@ -216,88 +218,92 @@ public static double getHealth(ActorModel npc) {
216218 */
217219 public static Stream <Rs2NpcModel > getNpcs (Predicate <Rs2NpcModel > predicate ) {
218220 try {
219- // Defensive null checks for client and world view
220- if (Microbot .getClient () == null ) {
221- log .warn ("Client is null, returning empty NPC stream" );
222- return Stream .empty ();
223- }
224-
225- if (Microbot .getClient ().getTopLevelWorldView () == null ) {
226- log .warn ("TopLevelWorldView is null, returning empty NPC stream" );
227- return Stream .empty ();
228- }
229-
230- if (Microbot .getClient ().getTopLevelWorldView ().npcs () == null ) {
231- log .warn ("NPCs collection is null, returning empty NPC stream" );
232- return Stream .empty ();
233- }
234-
235- if (Microbot .getClient ().getLocalPlayer () == null ) {
236- log .warn ("Local player is null, returning empty NPC stream" );
237- return Stream .empty ();
238- }
239-
240- if (Microbot .getClient ().getLocalPlayer ().getLocalLocation () == null ) {
241- log .warn ("Local player location is null, returning empty NPC stream" );
242- return Stream .empty ();
243- }
244-
245- // Make local copies to avoid null issues during stream processing
246- final Stream <? extends NPC > npcStream = Microbot .getClient ().getTopLevelWorldView ().npcs ().stream ();
247- final LocalPoint playerLocation = Microbot .getClient ().getLocalPlayer ().getLocalLocation ();
248-
249- // Safe predicate wrapper to prevent null issues
250- Predicate <Rs2NpcModel > safePredicate = predicate != null ? predicate : (npc -> true );
251- List <Rs2NpcModel > npcList = npcStream
252- .filter (Objects ::nonNull ) // Filter out null NPCs
253- .map (npc -> {
254- try {
255- return new Rs2NpcModel (npc );
256- } catch (Exception e ) {
257- log .debug ("Error creating Rs2NpcModel: {}" , e .getMessage ());
258- return null ;
259- }
260- })
261- .filter (Objects ::nonNull ) // Filter out failed model creations
262- .filter (npcModel -> {
263- try {
264- // Additional safety checks for Rs2NpcModel
265- return npcModel .getName () != null &&
266- npcModel .getLocalLocation () != null ;
267- } catch (Exception e ) {
268- log .debug ("Error accessing Rs2NpcModel properties: {}" , e .getMessage ());
269- return false ;
270- }
271- })
272- .filter (npcModel -> {
273- try {
274- return safePredicate .test (npcModel );
275- } catch (Exception e ) {
276- log .debug ("Error in predicate test: {}" , e .getMessage ());
277- return false ;
278- }
279- })
280- .sorted (Comparator .comparingInt (value -> {
281- try {
282- if (value != null && value .getLocalLocation () != null && playerLocation != null ) {
283- return value .getLocalLocation ().distanceTo (playerLocation );
221+ // Execute all game object access on client thread to prevent race conditions
222+ Rs2NpcModel [] npcArray = Microbot .getClientThread ().runOnClientThreadOptional (() -> {
223+ // Defensive null checks for client and world view
224+ if (Microbot .getClient () == null ) {
225+ log .warn ("Client is null, returning empty NPC stream" );
226+ return EMPTY_ARRAY ;
227+ }
228+
229+ if (Microbot .getClient ().getTopLevelWorldView () == null ) {
230+ log .warn ("TopLevelWorldView is null, returning empty NPC stream" );
231+ return EMPTY_ARRAY ;
232+ }
233+
234+ if (Microbot .getClient ().getTopLevelWorldView ().npcs () == null ) {
235+ log .warn ("NPCs collection is null, returning empty NPC stream" );
236+ return EMPTY_ARRAY ;
237+ }
238+
239+ if (Microbot .getClient ().getLocalPlayer () == null ) {
240+ log .warn ("Local player is null, returning empty NPC stream" );
241+ return EMPTY_ARRAY ;
242+ }
243+
244+ if (Microbot .getClient ().getLocalPlayer ().getLocalLocation () == null ) {
245+ log .warn ("Local player location is null, returning empty NPC stream" );
246+ return EMPTY_ARRAY ;
247+ }
248+
249+ // Make local copies to avoid null issues during stream processing
250+ final Stream <? extends NPC > npcStream = Microbot .getClient ().getTopLevelWorldView ().npcs ().stream ();
251+ final LocalPoint playerLocation = Microbot .getClient ().getLocalPlayer ().getLocalLocation ();
252+
253+ // Safe predicate wrapper to prevent null issues
254+ Predicate <Rs2NpcModel > safePredicate = predicate != null ? predicate : (npc -> true );
255+ return npcStream
256+ .filter (Objects ::nonNull ) // Filter out null NPCs
257+ .map (npc -> {
258+ try {
259+ return new Rs2NpcModel (npc );
260+ } catch (Exception e ) {
261+ log .debug ("Error creating Rs2NpcModel: {}" , e .getMessage ());
262+ return null ;
263+ }
264+ })
265+ .filter (Objects ::nonNull ) // Filter out failed model creations
266+ .filter (npcModel -> {
267+ try {
268+ // Additional safety checks for Rs2NpcModel
269+ return npcModel .getName () != null &&
270+ npcModel .getLocalLocation () != null ;
271+ } catch (Exception e ) {
272+ log .debug ("Error accessing Rs2NpcModel properties: {}" , e .getMessage ());
273+ return false ;
284274 }
285- return Integer .MAX_VALUE ; // Put problematic NPCs at the end
286- } catch (Exception e ) {
287- log .debug ("Error calculating distance: {}" , e .getMessage ());
288- return Integer .MAX_VALUE ;
289- }
290- }))
291- .collect (Collectors .toList ());
292-
293- return npcList .stream ();
294-
275+ })
276+ .filter (npcModel -> {
277+ try {
278+ return safePredicate .test (npcModel );
279+ } catch (Exception e ) {
280+ log .debug ("Error in predicate test: {}" , e .getMessage ());
281+ return false ;
282+ }
283+ })
284+ .sorted (Comparator .comparingInt (value -> {
285+ try {
286+ if (value != null && value .getLocalLocation () != null && playerLocation != null ) {
287+ return value .getLocalLocation ().distanceTo (playerLocation );
288+ }
289+ return Integer .MAX_VALUE ; // Put problematic NPCs at the end
290+ } catch (Exception e ) {
291+ log .debug ("Error calculating distance: {}" , e .getMessage ());
292+ return Integer .MAX_VALUE ;
293+ }
294+ }))
295+ .toArray (Rs2NpcModel []::new );
296+ }).orElse (EMPTY_ARRAY );
297+
298+ // Convert array back to stream for API compatibility
299+ return Arrays .stream (npcArray );
300+
295301 } catch (Exception e ) {
296302 log .debug ("Unexpected error in getNpcs: {}" , e .getMessage (), e );
297303 return Stream .empty ();
298304 }
299305 }
300-
306+
301307 /**
302308 * Retrieves a stream of all NPCs in the game world.
303309 *
0 commit comments