using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Fusion; using Fusion.Sockets; using UnityEngine; using OnlyScove.Scripts; namespace Hallucinate.UI { public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks { private static BasicSpawner _instance; public static BasicSpawner Instance { get { if (_instance == null) { _instance = UnityEngine.Object.FindFirstObjectByType(); } return _instance; } } private NetworkRunner _runner; public NetworkRunner Runner => _runner; private bool _isStarting = false; private bool _isInternalShutdown = false; public event Action> OnSessionListUpdatedEvent; public event Action OnShutdownEvent; public event Action OnJoinStartedEvent; public event Action OnJoinFailedEvent; [Header("Prefabs")] [SerializeField] private NetworkPrefabRef _playerPrefab; [SerializeField] private NetworkPrefabRef _playerDataManagerPrefab; private void Awake() { if (_instance != null && _instance != this) { Destroy(gameObject); return; } _instance = this; // Ensure this is a root object so DontDestroyOnLoad works correctly transform.SetParent(null); DontDestroyOnLoad(gameObject); } public PlayerProfile LocalPlayerProfile { get; private set; } public void SetLocalPlayerProfile(PlayerProfile _profile) { LocalPlayerProfile = _profile; } private async Task EnsureRunnerExists() { if (_runner != null) { _isInternalShutdown = true; try { if (_runner.IsRunning) { Debug.Log("[BasicSpawner] Shutting down existing runner before recreation."); await _runner.Shutdown(); } // Check if it still exists (Unity pseudo-null check) if (_runner != null) { // Only log if it's actually a valid object to destroy // If it's already marked for destruction, Unity == null will be true soon Destroy(_runner); } _runner = null; await Task.Yield(); } finally { _isInternalShutdown = false; } } if (this == null) return; // BasicSpawner itself might be destroyed Debug.Log("[BasicSpawner] Creating new NetworkRunner component."); _runner = gameObject.AddComponent(); _runner.ProvideInput = true; _runner.AddCallbacks(this); } public async Task StartLobby() { if (_isStarting) return; // Nếu đã ở trong lobby rồi thì không cần làm gì if (_runner != null && _runner.IsRunning && _runner.LobbyInfo.IsValid) return; Debug.Log("[BasicSpawner] StartLobby called"); _isStarting = true; try { await EnsureRunnerExists(); Debug.Log("[BasicSpawner] Joining Lobby..."); var result = await _runner.JoinSessionLobby(SessionLobby.ClientServer); if (!result.Ok) { Debug.LogWarning($"Join lobby result: {result.ShutdownReason}"); } } finally { _isStarting = false; } } public async Task StartHost(string sessionName, string displayName, string password = null) { // Wait for any existing startup process (like StartLobby) to finish while (_isStarting) { await Task.Yield(); } _isStarting = true; try { Debug.Log($"[BasicSpawner] StartHost called: {sessionName} ({displayName})"); OnJoinStartedEvent?.Invoke(); bool sceneExists = false; for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCountInBuildSettings; i++) { if (UnityEngine.SceneManagement.SceneUtility.GetScenePathByBuildIndex(i).Contains("Main Scene")) { sceneExists = true; break; } } if (!sceneExists) { Debug.LogError("CRITICAL: 'Main Scene' is NOT in Build Settings!"); return false; } await EnsureRunnerExists(); var customProps = new Dictionary(); if (!string.IsNullOrEmpty(password)) { customProps.Add("pw", password); } customProps.Add("rn", displayName); // Re-create or find SceneManager to ensure it matches the new runner var sceneManager = gameObject.GetComponent(); if (sceneManager == null) sceneManager = gameObject.AddComponent(); var result = await _runner.StartGame(new StartGameArgs() { GameMode = GameMode.Host, SessionName = sessionName, SessionProperties = customProps, PlayerCount = 2, SceneManager = sceneManager }); if (result.Ok) { Debug.Log("[BasicSpawner] StartHost SUCCESS"); if (_runner.IsServer && _playerDataManagerPrefab.IsValid) { if (FindFirstObjectByType() == null) { Debug.Log("[BasicSpawner] Spawning PlayerDataManager"); _runner.Spawn(_playerDataManagerPrefab, Vector3.zero, Quaternion.identity, null); } } return true; } else { Debug.LogError($"[BasicSpawner] Fusion StartHost Failed: {result.ShutdownReason}."); OnJoinFailedEvent?.Invoke(); return false; } } finally { _isStarting = false; } } public async Task StartClient(string sessionName, string password = null) { if (_isStarting) return false; _isStarting = true; try { OnJoinStartedEvent?.Invoke(); await EnsureRunnerExists(); var sceneManager = gameObject.GetComponent(); if (sceneManager == null) sceneManager = gameObject.AddComponent(); var result = await _runner.StartGame(new StartGameArgs() { GameMode = GameMode.Client, SessionName = sessionName, SceneManager = sceneManager }); if (result.Ok) { return true; } else { Debug.LogError($"[BasicSpawner] Fusion StartClient Failed: {result.ShutdownReason}"); OnJoinFailedEvent?.Invoke(); return false; } } finally { _isStarting = false; } } private Dictionary _spawnedCharacters = new Dictionary(); public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { if (player == runner.LocalPlayer) { SendLocalMetaData(player); } } private async void SendLocalMetaData(PlayerRef player) { PlayerDataManager pdm = null; int retries = 0; while (pdm == null && retries < 20) { pdm = FindFirstObjectByType(); if (pdm != null) break; await Task.Delay(500); retries++; } if (pdm != null) { string playerName = LocalPlayerProfile != null ? LocalPlayerProfile.Name : "Player " + player.PlayerId; // Thêm hậu tố (HOST) nếu là server để dễ phân biệt if (_runner.IsServer) playerName += " (HOST)"; _Role playerRole = _Role.Seeker; var metaData = new _PlayerMetaData() { Name = playerName, Role = playerRole, IsReady = false }; pdm.RPC_UpdatePlayerMetaData(player, metaData); } else { Debug.LogError("[BasicSpawner] Could not find PlayerDataManager after retries. Data will not sync."); } } public void StartGame() { if (_runner != null && _runner.IsServer) { _runner.LoadScene("Main Scene"); } } public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject)) { runner.Despawn(networkObject); _spawnedCharacters.Remove(player); } // Logic Reassign Leader (Logical) if (runner.IsServer && PlayerDataManager.Instance != null && PlayerDataManager.Instance.Leader == player) { var nextLeader = runner.ActivePlayers.FirstOrDefault(); if (nextLeader != PlayerRef.None) { PlayerDataManager.Instance.Leader = nextLeader; Debug.Log($"[BasicSpawner] Leader left. New logical leader: {nextLeader}"); } } if (runner.IsServer && player == runner.LocalPlayer) { runner.Shutdown(); } } public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { Debug.LogWarning($"[Fusion] Shutdown occurred. Reason: {shutdownReason}"); OnShutdownEvent?.Invoke(shutdownReason.ToString()); // Nếu shutdown là do hệ thống chủ động hủy để tạo runner mới, KHÔNG quay về Menu if (_isInternalShutdown) { Debug.Log("[BasicSpawner] Internal shutdown detected, skipping Menu routing."); return; } // Nếu đang trong quá trình Host Migration, đừng quay về menu if (shutdownReason == ShutdownReason.HostMigration) { Debug.Log("[BasicSpawner] Shutdown due to Host Migration. Waiting for recovery..."); return; } if (UIManager.Instance != null) { UIManager.Instance.OnBackToMenu(); } } public void OnSessionListUpdated(NetworkRunner runner, List sessionList) { OnSessionListUpdatedEvent?.Invoke(sessionList); } public void OnInput(NetworkRunner runner, NetworkInput input) { // var data = new PlayerInputData(); // if (PlayerStateMachine.Local != null && PlayerStateMachine.Local.Input != null) // { // data.Direction = PlayerStateMachine.Local.Input.MoveInput; // data.sprint = PlayerStateMachine.Local.Input.IsSprintHeld; // data.jump = PlayerStateMachine.Local.Input.ConsumeJumpInput(); // if (PlayerStateMachine.Local.Cam != null) // data.rot = PlayerStateMachine.Local.Cam.PlanarRotation; // } // input.Set(data); } public void OnConnectedToServer(NetworkRunner runner) { } public void OnDisconnectedFromServer(NetworkRunner runner, NetDisconnectReason reason) { } public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { } public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason) { } public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { } public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment data) { } public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress) { } public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { } public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { } public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { } public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary data) { } public async void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { Debug.Log("[BasicSpawner] OnHostMigration triggered!"); // 1. Shutdown existing runner properly await runner.Shutdown(false); // 2. Create new runner await EnsureRunnerExists(); // 3. Restart as new Host/Server using the migration token var result = await _runner.StartGame(new StartGameArgs() { HostMigrationToken = hostMigrationToken, SceneManager = gameObject.GetComponent() ?? gameObject.AddComponent() }); if (result.Ok) { Debug.Log("[BasicSpawner] Host Migration SUCCESSFUL"); } else { Debug.LogError($"[BasicSpawner] Host Migration FAILED: {result.ShutdownReason}"); UIManager.Instance?.OnBackToMenu(); } } public void OnSceneLoadDone(NetworkRunner runner) { string currentSceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; if (runner.IsServer && currentSceneName == "Main Scene") { foreach (var player in runner.ActivePlayers) { Vector2 spawnPosition = (player == runner.LocalPlayer) ? new Vector2(-8, 0) : new Vector2(8, 0); var networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player); _spawnedCharacters.Add(player, networkPlayerObject); } } if (currentSceneName == "Main Scene") { UIManager.Instance?.OnGameStarted(); } } public void OnSceneLoadStart(NetworkRunner runner) { } } }