Skip to content

Commit bf63e88

Browse files
committed
[F] Initial Fix NullReferenceException Crash
- Initial try on fixing NullReferenceException crash when player first time creating a room with better thread management(maybe). - Change client side WorldLink info display format
1 parent 4b0ad62 commit bf63e88

File tree

1 file changed

+105
-25
lines changed

1 file changed

+105
-25
lines changed

mod/WorldLink/FutariPatch.cs

Lines changed: 105 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)