using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.Networking; using Newtonsoft.Json; public class AvatarSyncClient : MonoBehaviour { [Header("Server Configuration")] [SerializeField] private string serverHost = "127.0.0.1"; [SerializeField] private int serverPort = 8080; [SerializeField] private string targetPlayer = "player1"; // Which player data to fetch [Header("Sync Configuration")] [SerializeField] private bool autoSync = true; [SerializeField] private float syncInterval = 0.1f; // How often to fetch data (in seconds) [SerializeField] private string localFileName = ""; // Leave empty to use targetPlayer name [Header("Upload Configuration")] [SerializeField] private bool uploadLocalData = false; // Upload this client's data to server [SerializeField] private string localDataFile = "avatar_sync_data.json"; // Local file to upload [SerializeField] private string uploadAsPlayer = "player2"; // Upload as which player [Header("Debug")] [SerializeField] private bool enableDebugMode = false; [SerializeField] private bool showConnectionStatus = true; private string syncFilesPath; private string outputFilePath; private Coroutine syncCoroutine; private bool isConnected = false; private float lastSuccessfulSync = 0f; private int totalRequests = 0; private int successfulRequests = 0; // Connection status public bool IsConnected => isConnected; public string ServerUrl => $"http://{serverHost}:{serverPort}"; public float LastSyncTime => lastSuccessfulSync; public float SuccessRate => totalRequests > 0 ? (float)successfulRequests / totalRequests : 0f; void Start() { // Set up file paths syncFilesPath = Path.Combine(Application.dataPath, "Sync-Files"); if (!Directory.Exists(syncFilesPath)) { Directory.CreateDirectory(syncFilesPath); } // Determine output file name string fileName = string.IsNullOrEmpty(localFileName) ? $"{targetPlayer}.json" : localFileName; outputFilePath = Path.Combine(syncFilesPath, fileName); Debug.Log($"Avatar Sync Client initialized"); Debug.Log($"Server: {ServerUrl}"); Debug.Log($"Target Player: {targetPlayer}"); Debug.Log($"Output File: {outputFilePath}"); Debug.Log($"Upload Mode: {(uploadLocalData ? $"Yes (as {uploadAsPlayer})" : "No")}"); // Start syncing if auto-sync is enabled if (autoSync) { StartSync(); } // Test connection StartCoroutine(TestConnection()); } void OnValidate() { // Ensure valid values if (syncInterval < 0.01f) syncInterval = 0.01f; if (serverPort < 1 || serverPort > 65535) serverPort = 8080; } public void StartSync() { if (syncCoroutine == null) { syncCoroutine = StartCoroutine(SyncLoop()); Debug.Log("Avatar sync started"); } } public void StopSync() { if (syncCoroutine != null) { StopCoroutine(syncCoroutine); syncCoroutine = null; Debug.Log("Avatar sync stopped"); } } IEnumerator TestConnection() { string url = $"{ServerUrl}/status"; using (UnityWebRequest request = UnityWebRequest.Get(url)) { request.timeout = 5; // 5 second timeout for connection test yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { isConnected = true; if (showConnectionStatus) { Debug.Log($"Successfully connected to avatar sync server at {ServerUrl}"); } // Try to parse server status try { var status = JsonConvert.DeserializeObject>(request.downloadHandler.text); if (enableDebugMode) { Debug.Log($"Server status: {request.downloadHandler.text}"); } } catch (Exception e) { if (enableDebugMode) { Debug.LogWarning($"Could not parse server status: {e.Message}"); } } } else { isConnected = false; if (showConnectionStatus) { Debug.LogWarning($"Failed to connect to avatar sync server at {ServerUrl}: {request.error}"); } } } } IEnumerator SyncLoop() { while (true) { // Upload local data if enabled if (uploadLocalData) { yield return StartCoroutine(UploadLocalData()); } // Fetch remote player data yield return StartCoroutine(FetchPlayerData()); yield return new WaitForSeconds(syncInterval); } } IEnumerator FetchPlayerData() { string url = $"{ServerUrl}/{targetPlayer}"; totalRequests++; using (UnityWebRequest request = UnityWebRequest.Get(url)) { request.timeout = 10; // 10 second timeout yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { try { // Validate JSON var avatarData = JsonConvert.DeserializeObject(request.downloadHandler.text); if (avatarData != null) { // Write to local file File.WriteAllText(outputFilePath, request.downloadHandler.text); successfulRequests++; lastSuccessfulSync = Time.time; isConnected = true; if (enableDebugMode) { Debug.Log($"Successfully fetched and saved data for {targetPlayer}. Timestamp: {avatarData.timestamp}"); } } else { if (enableDebugMode) { Debug.LogWarning($"Received null avatar data for {targetPlayer}"); } } } catch (Exception e) { Debug.LogError($"Error processing avatar data for {targetPlayer}: {e.Message}"); } } else { isConnected = false; if (enableDebugMode) { Debug.LogWarning($"Failed to fetch data for {targetPlayer}: {request.error}"); } } } } IEnumerator UploadLocalData() { string localFilePath = Path.Combine(syncFilesPath, localDataFile); if (!File.Exists(localFilePath)) { if (enableDebugMode) { Debug.LogWarning($"Local data file not found: {localFilePath}"); } yield break; } string jsonData = null; AvatarSyncData avatarData = null; // Read and validate file outside of try block with yield try { jsonData = File.ReadAllText(localFilePath); avatarData = JsonConvert.DeserializeObject(jsonData); } catch (Exception e) { Debug.LogError($"Error reading/parsing local data file: {e.Message}"); yield break; } if (avatarData == null) { if (enableDebugMode) { Debug.LogWarning("Local avatar data is invalid, skipping upload"); } yield break; } // Network request without try-catch since we can't yield in try-catch string url = $"{ServerUrl}/{uploadAsPlayer}"; using (UnityWebRequest request = new UnityWebRequest(url, "POST")) { byte[] jsonBytes = System.Text.Encoding.UTF8.GetBytes(jsonData); request.uploadHandler = new UploadHandlerRaw(jsonBytes); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); request.timeout = 10; yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { if (enableDebugMode) { Debug.Log($"Successfully uploaded data as {uploadAsPlayer}"); } } else { if (enableDebugMode) { Debug.LogWarning($"Failed to upload data as {uploadAsPlayer}: {request.error}"); } } } } // Public methods for runtime control public void SetServerAddress(string host, int port) { serverHost = host; serverPort = port; // Test new connection StartCoroutine(TestConnection()); } public void SetTargetPlayer(string player) { targetPlayer = player; // Update output file path string fileName = string.IsNullOrEmpty(localFileName) ? $"{targetPlayer}.json" : localFileName; outputFilePath = Path.Combine(syncFilesPath, fileName); } public void ManualSync() { if (gameObject.activeInHierarchy) { StartCoroutine(FetchPlayerData()); } } // Get current stats for UI display public string GetConnectionStats() { return $"Connected: {isConnected}\n" + $"Success Rate: {(SuccessRate * 100):F1}%\n" + $"Total Requests: {totalRequests}\n" + $"Last Sync: {(Time.time - lastSuccessfulSync):F1}s ago"; } void OnDisable() { StopSync(); } void OnDestroy() { StopSync(); } // GUI for runtime debugging void OnGUI() { if (!enableDebugMode) return; GUILayout.BeginArea(new Rect(10, 10, 300, 200)); GUILayout.BeginVertical("box"); GUILayout.Label($"Avatar Sync Client - {targetPlayer}"); GUILayout.Label($"Server: {ServerUrl}"); GUILayout.Label($"Status: {(isConnected ? "Connected" : "Disconnected")}"); GUILayout.Label($"Success Rate: {(SuccessRate * 100):F1}%"); GUILayout.Label($"Last Sync: {(Time.time - lastSuccessfulSync):F1}s ago"); GUILayout.BeginHorizontal(); if (GUILayout.Button("Manual Sync")) { ManualSync(); } if (GUILayout.Button("Test Connection")) { StartCoroutine(TestConnection()); } GUILayout.EndHorizontal(); if (syncCoroutine == null) { if (GUILayout.Button("Start Auto Sync")) { StartSync(); } } else { if (GUILayout.Button("Stop Auto Sync")) { StopSync(); } } GUILayout.EndVertical(); GUILayout.EndArea(); } }