@@ -42,6 +42,10 @@ public static class Futari
4242 private static MethodBase packetWriteUInt ;
4343 private static System . Type StartUpStateType ;
4444
45+ // Thread management
46+ private static System . Threading . Thread onlineUserCountThread ;
47+ private static System . Threading . Thread recruitListThread ;
48+
4549 [ ConfigEntry ( hideWhenDefault : true ) ]
4650 public static bool Debug = false ;
4751
@@ -100,11 +104,14 @@ public static bool CheckAuth_Proc()
100104 client . ConnectAsync ( ) ;
101105 isInit = true ;
102106
107+ // Wait a bit for game systems to fully initialize
108+ Thread . Sleep ( 2000 ) ;
109+
103110 // Fetch initial online user count
104111 FetchOnlineUserCount ( ) ;
105112
106- // Start periodic online user count fetching
107- 30000 . Interval ( ( ) => FetchOnlineUserCount ( ) ) ;
113+ // Start periodic online user count fetching with proper thread management
114+ onlineUserCountThread = 30000 . Interval ( ( ) => FetchOnlineUserCount ( ) , name : "OnlineUserCountThread" ) ;
108115 } ) . Start ( ) ;
109116
110117 return PrefixRet . RUN_ORIGINAL ;
@@ -127,11 +134,29 @@ private static bool PreIsLanAvailable(ref bool __result)
127134 // Fetch online user count from server
128135 private static void FetchOnlineUserCount ( )
129136 {
137+ if ( stopping ) return ; // Don't fetch if we're shutting down
138+
130139 try
131140 {
141+ // Check if the lobby URL is available
142+ if ( string . IsNullOrEmpty ( FutariClient . LOBBY_BASE ) )
143+ {
144+ Log . Debug ( "Lobby URL not available yet, skipping online user count fetch" ) ;
145+ return ;
146+ }
147+
132148 var response = $ "{ FutariClient . LOBBY_BASE } /online". Get ( ) ;
149+ if ( string . IsNullOrEmpty ( response ) )
150+ {
151+ Log . Debug ( "Empty response from online endpoint" ) ;
152+ return ;
153+ }
154+
133155 var onlineInfo = JsonUtility . FromJson < OnlineUserInfo > ( response ) ;
134- onlineUserCount = onlineInfo . totalUsers ;
156+ if ( onlineInfo != null )
157+ {
158+ onlineUserCount = onlineInfo . totalUsers ;
159+ }
135160 }
136161 catch ( Exception ex )
137162 {
@@ -165,12 +190,12 @@ public static void CommonMonitorViewUpdate(CommonMonitor __instance,TextMeshProU
165190 var recruitCount = PartyMan == null ? 0 : PartyMan . GetRecruitList ( ) . Count ;
166191 if ( onlineUserCount > 0 )
167192 {
168- ____buildVersionText . text = $ "WorldLink Recruiting: { recruitCount } / { onlineUserCount } ";
193+ ____buildVersionText . text = $ "[WL] Room: { recruitCount } | Online: { onlineUserCount } ";
169194 ____buildVersionText . color = recruitCount > 0 ? Color . green : Color . cyan ;
170195 }
171196 else if ( onlineUserCount == 0 )
172197 {
173- ____buildVersionText . text = $ "No players online ";
198+ ____buildVersionText . text = $ "[WL] Online:0 ";
174199 ____buildVersionText . color = Color . gray ;
175200 }
176201 else
@@ -263,26 +288,81 @@ public static bool preMyIpAddress(int mockID, ref IPAddress __result)
263288 public static void postClientConstruct ( Client __instance , string name , PartyLink . Party . InitParam initParam )
264289 {
265290 Log . Debug ( $ "new Client({ name } , { initParam } )") ;
266- 10000 . Interval ( ( ) =>
267- lastRecruits = $ "{ FutariClient . LOBBY_BASE } /recruit/list"
268- . Get ( ) . Trim ( ) . Split ( '\n ' )
269- . Where ( x => ! string . IsNullOrEmpty ( x ) )
270- . Select ( JsonUtility . FromJson < RecruitRecord > ) . ToList ( )
271- . Also ( lst => lst
272- . Select ( x => x . RecruitInfo . Identity ( ) )
273- . Do ( ids => lastRecruits . Keys
274- . Where ( key => ! ids . Contains ( key ) )
275- . Each ( key => RFinishRecruit . Invoke ( __instance , new [ ] { new Packet ( lastRecruits [ key ] . IpAddress )
276- . Also ( p => p . encode ( new FinishRecruit ( lastRecruits [ key ] ) ) )
277- } ) )
278- )
279- )
280- . Each ( x => RStartRecruit . Invoke ( __instance , new [ ] { new Packet ( x . RecruitInfo . IpAddress )
281- . Also ( p => p . encode ( new StartRecruit ( x . RecruitInfo ) ) )
282- } ) )
283- . Select ( x => x . RecruitInfo )
284- . ToDictionary ( x => x . Identity ( ) )
285- ) ;
291+ recruitListThread = 10000 . Interval ( ( ) =>
292+ {
293+ if ( stopping ) return ; // Don't process if we're shutting down
294+
295+ try
296+ {
297+ // Add null checks to prevent crashes
298+ if ( __instance == null || lastRecruits == null )
299+ {
300+ Log . Debug ( "Client or lastRecruits is null, skipping recruit list update" ) ;
301+ return ;
302+ }
303+
304+ var response = $ "{ FutariClient . LOBBY_BASE } /recruit/list". Get ( ) ;
305+ if ( string . IsNullOrEmpty ( response ) )
306+ {
307+ Log . Debug ( "Empty response from recruit list endpoint" ) ;
308+ return ;
309+ }
310+
311+ var recruitRecords = response . Trim ( ) . Split ( '\n ' )
312+ . Where ( x => ! string . IsNullOrEmpty ( x ) )
313+ . Select ( JsonUtility . FromJson < RecruitRecord > )
314+ . Where ( x => x != null && x . RecruitInfo != null ) // Filter out null records
315+ . ToList ( ) ;
316+
317+ // Process finished recruits
318+ var currentIds = recruitRecords . Select ( x => x . RecruitInfo . Identity ( ) ) . ToList ( ) ;
319+ var finishedRecruits = lastRecruits . Keys . Where ( key => ! currentIds . Contains ( key ) ) . ToList ( ) ;
320+
321+ foreach ( var key in finishedRecruits )
322+ {
323+ try
324+ {
325+ if ( lastRecruits . ContainsKey ( key ) && lastRecruits [ key ] != null )
326+ {
327+ var packet = new Packet ( lastRecruits [ key ] . IpAddress ) ;
328+ packet . encode ( new FinishRecruit ( lastRecruits [ key ] ) ) ;
329+ RFinishRecruit ? . Invoke ( __instance , new object [ ] { packet } ) ;
330+ }
331+ }
332+ catch ( Exception ex )
333+ {
334+ Log . Error ( $ "Error processing finished recruit { key } : { ex . Message } ") ;
335+ }
336+ }
337+
338+ // Process new recruits
339+ foreach ( var record in recruitRecords )
340+ {
341+ try
342+ {
343+ if ( record . RecruitInfo != null )
344+ {
345+ var packet = new Packet ( record . RecruitInfo . IpAddress ) ;
346+ packet . encode ( new StartRecruit ( record . RecruitInfo ) ) ;
347+ RStartRecruit ? . Invoke ( __instance , new object [ ] { packet } ) ;
348+ }
349+ }
350+ catch ( Exception ex )
351+ {
352+ Log . Error ( $ "Error processing new recruit: { ex . Message } ") ;
353+ }
354+ }
355+
356+ // Update lastRecruits dictionary
357+ lastRecruits = recruitRecords
358+ . Where ( x => x . RecruitInfo != null )
359+ . ToDictionary ( x => x . RecruitInfo . Identity ( ) , x => x . RecruitInfo ) ;
360+ }
361+ catch ( Exception ex )
362+ {
363+ Log . Error ( $ "Error in recruit list update: { ex . Message } ") ;
364+ }
365+ } , name : "RecruitListThread" ) ;
286366 }
287367
288368 // Block start recruit if the song is not available
0 commit comments