-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPlayerDetection.cs
More file actions
362 lines (317 loc) Β· 12.9 KB
/
PlayerDetection.cs
File metadata and controls
362 lines (317 loc) Β· 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
using System;
using System.Collections;
using MelonLoader;
using UnityEngine;
using Il2CppScheduleOne.PlayerScripts;
using Il2CppScheduleOne.Levelling;
namespace PaxDrops
{
/// <summary>
/// Centralized player detection system that uses event-driven detection instead of polling.
/// Hooks into Unity lifecycle events to detect when the player becomes available.
/// </summary>
public static class PlayerDetection
{
/// <summary>
/// Event triggered when the player is successfully detected and validated
/// </summary>
public static event Action<Player>? OnPlayerLoaded;
/// <summary>
/// Event triggered when player rank data becomes available
/// </summary>
public static event Action<Player, ERank>? OnPlayerRankLoaded;
/// <summary>
/// Current detected player instance (null if not found yet)
/// </summary>
public static Player? CurrentPlayer { get; private set; }
/// <summary>
/// Current player's rank (Street_Rat if not detected yet)
/// </summary>
public static ERank CurrentRank { get; private set; } = ERank.Street_Rat;
/// <summary>
/// Whether the player has been successfully detected
/// </summary>
public static bool IsPlayerDetected => CurrentPlayer != null;
/// <summary>
/// Whether player rank data is available
/// </summary>
public static bool IsRankDetected { get; private set; } = false;
private static bool _detectionStarted = false;
private static bool _detectionComplete = false;
private static int _checkAttempts = 0;
/// <summary>
/// Start the event-driven player detection system
/// </summary>
public static void StartDetection()
{
if (_detectionStarted)
{
Logger.Warn("Detection already started!", "PlayerDetection");
return;
}
_detectionStarted = true;
Logger.Debug("π΅οΈ Starting event-driven player detection...", "PlayerDetection");
// Start with an initial check and fallback timer
MelonCoroutines.Start(InitialDetectionCheck());
}
/// <summary>
/// Initial detection check with fallback - only runs a few times instead of continuous polling
/// </summary>
private static IEnumerator InitialDetectionCheck()
{
Logger.Debug("π Starting initial detection checks", "PlayerDetection");
// Wait for scene to settle
yield return new WaitForSeconds(1f);
// Try detection immediately
if (TryDetectPlayerComplete())
yield break;
// If not found, try a few more times with increasing delays
for (int attempt = 1; attempt <= 5; attempt++)
{
yield return new WaitForSeconds(attempt * 2f); // 2s, 4s, 6s, 8s, 10s
Logger.Debug($"Detection attempt {attempt}/5", "PlayerDetection");
if (TryDetectPlayerComplete())
yield break;
}
// If still not found after initial attempts, set up periodic checks (much less frequent)
Logger.Debug("π‘ Setting up periodic detection checks", "PlayerDetection");
MelonCoroutines.Start(PeriodicDetectionFallback());
}
/// <summary>
/// Fallback periodic checks - only every 10 seconds instead of 0.5 seconds
/// </summary>
private static IEnumerator PeriodicDetectionFallback()
{
while (!_detectionComplete && _checkAttempts < 30) // Max 5 minutes
{
yield return new WaitForSeconds(10f); // Much less frequent polling
_checkAttempts++;
Logger.Debug($"Periodic check {_checkAttempts}/30", "PlayerDetection");
if (TryDetectPlayerComplete())
yield break;
}
if (!_detectionComplete)
{
Logger.Warn("β οΈ Player detection failed after all attempts", "PlayerDetection");
}
}
/// <summary>
/// Try to detect player and rank in one go
/// </summary>
private static bool TryDetectPlayerComplete()
{
try
{
var player = TryDetectPlayer();
if (player == null) return false;
Logger.Debug($"β
Player detected: {player.PlayerName}", "PlayerDetection");
CurrentPlayer = player;
OnPlayerLoaded?.Invoke(player);
var rank = TryDetectPlayerRank(player);
Logger.Debug($"β
Player rank detected: {rank}", "PlayerDetection");
CurrentRank = rank;
IsRankDetected = true;
OnPlayerRankLoaded?.Invoke(player, rank);
_detectionComplete = true;
Logger.Debug("π― Player detection complete!", "PlayerDetection");
return true;
}
catch (Exception ex)
{
Logger.Debug($"Detection attempt failed: {ex.Message}", "PlayerDetection");
return false;
}
}
/// <summary>
/// Try to detect the player using multiple methods
/// </summary>
private static Player? TryDetectPlayer()
{
// Method 1: Player.Local
try
{
var localPlayer = Player.Local;
if (IsPlayerValid(localPlayer))
{
Logger.Debug($"Found valid Player.Local: {localPlayer.PlayerName}", "PlayerDetection");
return localPlayer;
}
}
catch (Exception ex)
{
Logger.Debug($"Player.Local failed: {ex.Message}", "PlayerDetection");
}
// Method 2: FindObjectOfType
try
{
var players = UnityEngine.Object.FindObjectsOfType<Player>();
if (players != null && players.Length > 0)
{
foreach (var player in players)
{
if (IsPlayerValid(player) && player.IsLocalPlayer)
{
Logger.Debug($"Found valid local player via FindObjectsOfType: {player.PlayerName}", "PlayerDetection");
return player;
}
}
}
}
catch (Exception ex)
{
Logger.Debug($"FindObjectsOfType failed: {ex.Message}", "PlayerDetection");
}
return null;
}
/// <summary>
/// Try to detect the player's current rank
/// </summary>
private static ERank TryDetectPlayerRank(Player player)
{
if (player == null) return ERank.Street_Rat;
try
{
// Method 1: Try LevelManager component on player
var playerLevelManager = player.gameObject.GetComponent<LevelManager>();
if (playerLevelManager != null)
{
var rank = playerLevelManager.Rank;
var totalXP = playerLevelManager.TotalXP;
var tier = playerLevelManager.Tier;
Logger.Debug($"Player LevelManager: Rank={rank}, TotalXP={totalXP}, Tier={tier}", "PlayerDetection");
if (rank != ERank.Street_Rat || totalXP > 0 || tier > 1)
{
return rank;
}
}
// Method 2: Try LevelManager.Instance
var globalLevelManager = LevelManager.Instance;
if (globalLevelManager != null)
{
// Try GetFullRank first
try
{
var fullRank = globalLevelManager.GetFullRank();
Logger.Debug($"GlobalLM GetFullRank: Rank={fullRank.Rank}, Tier={fullRank.Tier}", "PlayerDetection");
if (fullRank.Rank != ERank.Street_Rat || fullRank.Tier > 1)
{
return fullRank.Rank;
}
}
catch (Exception ex)
{
Logger.Debug($"GetFullRank failed: {ex.Message}", "PlayerDetection");
}
// Try direct properties
var rank = globalLevelManager.Rank;
var totalXP = globalLevelManager.TotalXP;
var tier = globalLevelManager.Tier;
Logger.Debug($"GlobalLM Direct: Rank={rank}, TotalXP={totalXP}, Tier={tier}", "PlayerDetection");
if (totalXP > 1000 || tier > 1)
{
// Try calculating from XP
try
{
var calculatedRank = globalLevelManager.GetFullRank(totalXP);
Logger.Debug($"Calculated from XP: Rank={calculatedRank.Rank}", "PlayerDetection");
return calculatedRank.Rank;
}
catch (Exception ex)
{
Logger.Debug($"XP calculation failed: {ex.Message}", "PlayerDetection");
}
}
return rank;
}
}
catch (Exception ex)
{
Logger.Debug($"Rank detection failed: {ex.Message}", "PlayerDetection");
}
return ERank.Street_Rat;
}
/// <summary>
/// Manual check for player - can be called from external events
/// </summary>
public static void CheckForPlayer()
{
if (_detectionComplete) return;
Logger.Debug("π Manual player check triggered", "PlayerDetection");
TryDetectPlayerComplete();
}
/// <summary>
/// Validate that a player instance is properly initialized
/// </summary>
private static bool IsPlayerValid(Player? player)
{
try
{
return player != null &&
player.gameObject != null &&
player.gameObject.activeInHierarchy &&
!string.IsNullOrEmpty(player.PlayerName);
}
catch (Exception ex)
{
Logger.Debug($"Player validation failed: {ex.Message}", "PlayerDetection");
return false;
}
}
/// <summary>
/// Force refresh player rank (useful for testing or manual refresh)
/// </summary>
public static void RefreshPlayerRank()
{
if (CurrentPlayer != null)
{
Logger.Debug("π Refreshing player rank...", "PlayerDetection");
var newRank = TryDetectPlayerRank(CurrentPlayer);
if (newRank != CurrentRank)
{
Logger.Debug($"π Rank changed: {CurrentRank} β {newRank}", "PlayerDetection");
CurrentRank = newRank;
OnPlayerRankLoaded?.Invoke(CurrentPlayer, newRank);
}
else
{
Logger.Debug($"Rank unchanged: {CurrentRank}", "PlayerDetection");
}
}
else
{
Logger.Warn("β οΈ Cannot refresh rank - no player detected", "PlayerDetection");
}
}
/// <summary>
/// Get player info summary for debugging
/// </summary>
public static string GetPlayerInfo()
{
if (!IsPlayerDetected)
{
return "No player detected";
}
try
{
return $"Player: {CurrentPlayer!.PlayerName} | Rank: {CurrentRank} | Valid: {IsPlayerValid(CurrentPlayer!)}";
}
catch (Exception ex)
{
return $"Player info error: {ex.Message}";
}
}
/// <summary>
/// Reset detection state (for testing)
/// </summary>
public static void Reset()
{
Logger.Debug("π Resetting detection state", "PlayerDetection");
_detectionStarted = false;
_detectionComplete = false;
_checkAttempts = 0;
CurrentPlayer = null;
CurrentRank = ERank.Street_Rat;
IsRankDetected = false;
}
}
}