Files
BABA_YAGA/Assets/Scripts/Network/BasicSpawner.cs

446 lines
16 KiB
C#
Raw Normal View History

2026-04-30 15:45:37 +07:00
using System;
2026-04-11 18:13:40 +07:00
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Fusion;
using Fusion.Sockets;
using UnityEngine;
2026-04-29 13:10:00 +07:00
using OnlyScove.Scripts;
2026-04-11 18:13:40 +07:00
2026-04-28 19:04:09 +07:00
namespace Hallucinate.UI
2026-04-11 18:13:40 +07:00
{
2026-04-30 00:55:16 +07:00
public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
2026-04-28 19:04:09 +07:00
{
2026-05-01 20:07:12 +07:00
private static BasicSpawner _instance;
public static BasicSpawner Instance
{
get
{
if (_instance == null)
{
_instance = UnityEngine.Object.FindFirstObjectByType<BasicSpawner>();
}
return _instance;
}
}
2026-04-30 00:55:16 +07:00
private NetworkRunner _runner;
2026-05-01 17:57:07 +07:00
public NetworkRunner Runner => _runner;
private bool _isStarting = false;
2026-05-01 18:10:32 +07:00
private bool _isInternalShutdown = false;
2026-04-11 18:13:40 +07:00
2026-04-28 19:04:09 +07:00
public event Action<List<SessionInfo>> OnSessionListUpdatedEvent;
public event Action<string> OnShutdownEvent;
public event Action OnJoinStartedEvent;
public event Action OnJoinFailedEvent;
2026-04-11 18:13:40 +07:00
2026-04-30 15:45:37 +07:00
[Header("Prefabs")]
2026-04-30 15:08:19 +07:00
[SerializeField] private NetworkPrefabRef _playerPrefab;
[SerializeField] private NetworkPrefabRef _playerDataManagerPrefab;
2026-04-28 19:04:09 +07:00
private void Awake()
2026-04-11 18:13:40 +07:00
{
2026-05-01 20:07:12 +07:00
if (_instance != null && _instance != this)
2026-04-30 00:55:16 +07:00
{
Destroy(gameObject);
return;
}
2026-05-01 20:07:12 +07:00
_instance = this;
// Ensure this is a root object so DontDestroyOnLoad works correctly
transform.SetParent(null);
2026-04-28 19:04:09 +07:00
DontDestroyOnLoad(gameObject);
2026-04-11 18:13:40 +07:00
}
2026-04-28 19:04:09 +07:00
2026-04-30 00:55:16 +07:00
public PlayerProfile LocalPlayerProfile { get; private set; }
public void SetLocalPlayerProfile(PlayerProfile _profile)
2026-04-11 18:13:40 +07:00
{
2026-04-28 19:04:09 +07:00
LocalPlayerProfile = _profile;
2026-04-11 18:13:40 +07:00
}
2026-04-28 19:04:09 +07:00
2026-04-30 00:55:16 +07:00
private async Task EnsureRunnerExists()
2026-04-11 18:13:40 +07:00
{
2026-05-01 17:57:07 +07:00
if (_runner != null)
2026-04-30 01:30:58 +07:00
{
2026-05-01 18:10:32 +07:00
_isInternalShutdown = true;
try
2026-05-01 17:57:07 +07:00
{
2026-05-01 18:10:32 +07:00
if (_runner.IsRunning)
{
Debug.Log("[BasicSpawner] Shutting down existing runner before recreation.");
await _runner.Shutdown();
}
2026-05-01 21:58:20 +07:00
// 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);
}
2026-05-01 18:10:32 +07:00
_runner = null;
await Task.Yield();
}
finally
{
_isInternalShutdown = false;
2026-05-01 17:57:07 +07:00
}
2026-04-30 01:30:58 +07:00
}
2026-05-01 21:58:20 +07:00
if (this == null) return; // BasicSpawner itself might be destroyed
2026-05-01 17:57:07 +07:00
Debug.Log("[BasicSpawner] Creating new NetworkRunner component.");
_runner = gameObject.AddComponent<NetworkRunner>();
2026-04-28 19:04:09 +07:00
_runner.ProvideInput = true;
_runner.AddCallbacks(this);
2026-04-30 00:55:16 +07:00
}
public async Task StartLobby()
{
2026-05-01 17:57:07 +07:00
if (_isStarting) return;
2026-04-30 00:55:16 +07:00
2026-05-01 17:57:07 +07:00
// 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;
2026-04-30 15:08:19 +07:00
2026-05-01 17:57:07 +07:00
try
2026-04-30 00:55:16 +07:00
{
2026-05-01 17:57:07 +07:00
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;
2026-04-30 00:55:16 +07:00
}
2026-04-11 18:13:40 +07:00
}
2026-04-28 19:04:09 +07:00
2026-04-30 15:08:19 +07:00
public async Task<bool> StartHost(string sessionName, string displayName, string password = null)
2026-04-11 18:13:40 +07:00
{
2026-05-01 21:58:20 +07:00
// Wait for any existing startup process (like StartLobby) to finish
while (_isStarting)
{
await Task.Yield();
}
2026-05-01 17:57:07 +07:00
_isStarting = true;
2026-04-28 19:04:09 +07:00
2026-05-01 17:57:07 +07:00
try
2026-04-30 00:55:16 +07:00
{
2026-05-01 17:57:07 +07:00
Debug.Log($"[BasicSpawner] StartHost called: {sessionName} ({displayName})");
OnJoinStartedEvent?.Invoke();
bool sceneExists = false;
for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCountInBuildSettings; i++)
2026-04-30 00:55:16 +07:00
{
2026-05-01 17:57:07 +07:00
if (UnityEngine.SceneManagement.SceneUtility.GetScenePathByBuildIndex(i).Contains("Main Scene"))
{
sceneExists = true;
break;
}
2026-04-30 00:55:16 +07:00
}
2026-05-01 17:57:07 +07:00
if (!sceneExists)
{
Debug.LogError("CRITICAL: 'Main Scene' is NOT in Build Settings!");
return false;
}
2026-04-30 00:55:16 +07:00
2026-05-01 17:57:07 +07:00
await EnsureRunnerExists();
2026-04-30 00:55:16 +07:00
2026-05-01 17:57:07 +07:00
var customProps = new Dictionary<string, SessionProperty>();
if (!string.IsNullOrEmpty(password))
{
customProps.Add("pw", password);
}
customProps.Add("rn", displayName);
2026-04-28 19:04:09 +07:00
2026-05-01 17:57:07 +07:00
// Re-create or find SceneManager to ensure it matches the new runner
var sceneManager = gameObject.GetComponent<NetworkSceneManagerDefault>();
if (sceneManager == null) sceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>();
2026-04-28 19:04:09 +07:00
2026-05-01 17:57:07 +07:00
var result = await _runner.StartGame(new StartGameArgs()
{
GameMode = GameMode.Host,
SessionName = sessionName,
SessionProperties = customProps,
PlayerCount = 2,
SceneManager = sceneManager
});
if (result.Ok)
2026-04-30 15:08:19 +07:00
{
2026-05-01 17:57:07 +07:00
Debug.Log("[BasicSpawner] StartHost SUCCESS");
if (_runner.IsServer && _playerDataManagerPrefab.IsValid)
2026-04-30 15:08:19 +07:00
{
2026-05-01 17:57:07 +07:00
if (FindFirstObjectByType<PlayerDataManager>() == null)
{
Debug.Log("[BasicSpawner] Spawning PlayerDataManager");
_runner.Spawn(_playerDataManagerPrefab, Vector3.zero, Quaternion.identity, null);
}
2026-04-30 15:08:19 +07:00
}
2026-05-01 17:57:07 +07:00
return true;
}
else
{
Debug.LogError($"[BasicSpawner] Fusion StartHost Failed: {result.ShutdownReason}.");
OnJoinFailedEvent?.Invoke();
return false;
2026-04-30 15:08:19 +07:00
}
2026-04-30 00:55:16 +07:00
}
2026-05-01 17:57:07 +07:00
finally
2026-04-28 19:04:09 +07:00
{
2026-05-01 17:57:07 +07:00
_isStarting = false;
2026-04-28 19:04:09 +07:00
}
2026-04-11 18:13:40 +07:00
}
2026-04-28 19:04:09 +07:00
2026-04-30 00:55:16 +07:00
public async Task<bool> StartClient(string sessionName, string password = null)
2026-04-11 18:13:40 +07:00
{
2026-05-01 18:10:32 +07:00
if (_isStarting) return false;
_isStarting = true;
2026-05-01 17:57:07 +07:00
2026-05-01 18:10:32 +07:00
try
2026-04-28 19:04:09 +07:00
{
2026-05-01 18:10:32 +07:00
OnJoinStartedEvent?.Invoke();
await EnsureRunnerExists();
2026-04-11 18:13:40 +07:00
2026-05-01 18:10:32 +07:00
var sceneManager = gameObject.GetComponent<NetworkSceneManagerDefault>();
if (sceneManager == null) sceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>();
2026-05-01 17:57:07 +07:00
2026-05-01 18:10:32 +07:00
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;
}
2026-04-30 00:55:16 +07:00
}
2026-05-01 18:10:32 +07:00
finally
2026-04-28 19:04:09 +07:00
{
2026-05-01 18:10:32 +07:00
_isStarting = false;
2026-04-28 19:04:09 +07:00
}
}
2026-04-11 18:13:40 +07:00
2026-05-01 18:10:32 +07:00
2026-04-28 19:04:09 +07:00
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
if (player == runner.LocalPlayer)
{
2026-04-30 15:08:19 +07:00
SendLocalMetaData(player);
}
2026-05-12 21:43:03 +07:00
// Spawn player character if we are the server and in the game scene
if (runner.IsServer && UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "Main Scene")
{
SpawnPlayer(runner, player);
}
}
private void SpawnPlayer(NetworkRunner runner, PlayerRef player)
{
if (_spawnedCharacters.ContainsKey(player)) return;
// Simple spawn logic: Host on left, Client on right
// Using Vector3(..., 1, ...) to ensure player is above the ground
Vector3 spawnPosition = (player == runner.LocalPlayer) ? new Vector3(-8, 1, 0) : new Vector3(8, 1, 0);
Debug.Log($"[BasicSpawner] Spawning player {player} at {spawnPosition}");
var networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
_spawnedCharacters.Add(player, networkPlayerObject);
2026-04-30 15:08:19 +07:00
}
2026-04-30 00:55:16 +07:00
2026-04-30 15:08:19 +07:00
private async void SendLocalMetaData(PlayerRef player)
{
PlayerDataManager pdm = null;
int retries = 0;
while (pdm == null && retries < 20)
{
pdm = FindFirstObjectByType<PlayerDataManager>();
if (pdm != null) break;
await Task.Delay(500);
retries++;
}
2026-04-30 00:55:16 +07:00
2026-04-30 15:08:19 +07:00
if (pdm != null)
{
2026-04-30 15:45:37 +07:00
string playerName = LocalPlayerProfile != null ? LocalPlayerProfile.Name : "Player " + player.PlayerId;
2026-04-30 15:08:19 +07:00
// Thêm hậu tố (HOST) nếu là server để dễ phân biệt
2026-04-30 15:45:37 +07:00
if (_runner.IsServer) playerName += " (HOST)";
2026-04-30 15:08:19 +07:00
_Role playerRole = _Role.Seeker;
var metaData = new _PlayerMetaData()
{
Name = playerName,
Role = playerRole,
IsReady = false
};
pdm.RPC_UpdatePlayerMetaData(player, metaData);
}
else
{
2026-04-30 15:45:37 +07:00
Debug.LogError("[BasicSpawner] Could not find PlayerDataManager after retries. Data will not sync.");
2026-04-28 19:04:09 +07:00
}
2026-04-11 18:13:40 +07:00
}
2026-04-29 13:10:00 +07:00
public void StartGame()
{
2026-04-30 00:55:16 +07:00
if (_runner != null && _runner.IsServer)
2026-04-29 13:10:00 +07:00
{
2026-04-30 15:45:37 +07:00
_runner.LoadScene("Main Scene");
2026-04-29 13:10:00 +07:00
}
}
2026-04-28 19:04:09 +07:00
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
2026-04-11 18:13:40 +07:00
{
2026-04-28 19:04:09 +07:00
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
2026-04-11 18:13:40 +07:00
{
2026-05-12 21:43:03 +07:00
if (networkObject != null && networkObject.IsValid)
{
runner.Despawn(networkObject);
}
2026-04-28 19:04:09 +07:00
_spawnedCharacters.Remove(player);
2026-04-30 15:08:19 +07:00
}
2026-05-02 00:00:31 +07:00
// 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}");
}
}
2026-04-30 15:08:19 +07:00
if (runner.IsServer && player == runner.LocalPlayer)
{
runner.Shutdown();
2026-04-28 19:04:09 +07:00
}
2026-04-11 18:13:40 +07:00
}
2026-04-28 19:04:09 +07:00
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason)
2026-04-11 18:13:40 +07:00
{
2026-04-30 15:45:37 +07:00
Debug.LogWarning($"[Fusion] Shutdown occurred. Reason: {shutdownReason}");
2026-04-28 19:04:09 +07:00
OnShutdownEvent?.Invoke(shutdownReason.ToString());
2026-04-30 15:08:19 +07:00
2026-05-01 18:10:32 +07:00
// 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;
}
2026-05-02 00:00:31 +07:00
// 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;
}
2026-04-30 15:08:19 +07:00
if (UIManager.Instance != null)
{
UIManager.Instance.OnBackToMenu();
}
2026-04-11 18:13:40 +07:00
}
2026-04-28 19:04:09 +07:00
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
2026-04-11 18:13:40 +07:00
{
2026-04-28 19:04:09 +07:00
OnSessionListUpdatedEvent?.Invoke(sessionList);
2026-04-11 18:13:40 +07:00
}
2026-04-28 19:04:09 +07:00
public void OnInput(NetworkRunner runner, NetworkInput input)
2026-04-11 18:13:40 +07:00
{
2026-04-29 13:10:00 +07:00
var data = new PlayerInputData();
if (PlayerStateMachine.Local != null && PlayerStateMachine.Local.Input != null)
{
2026-04-30 00:55:16 +07:00
data.Direction = PlayerStateMachine.Local.Input.MoveInput;
data.sprint = PlayerStateMachine.Local.Input.IsSprintHeld;
2026-04-29 13:10:00 +07:00
if (PlayerStateMachine.Local.Cam != null)
data.rot = PlayerStateMachine.Local.Cam.PlanarRotation;
}
2026-04-28 19:04:09 +07:00
input.Set(data);
2026-04-11 18:13:40 +07:00
}
2026-04-23 08:56:35 +07:00
2026-04-28 19:04:09 +07:00
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<byte> 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<string, object> data) { }
2026-05-02 00:00:31 +07:00
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<NetworkSceneManagerDefault>() ?? gameObject.AddComponent<NetworkSceneManagerDefault>()
});
if (result.Ok)
{
Debug.Log("[BasicSpawner] Host Migration SUCCESSFUL");
}
else
{
Debug.LogError($"[BasicSpawner] Host Migration FAILED: {result.ShutdownReason}");
UIManager.Instance?.OnBackToMenu();
}
}
2026-04-29 13:10:00 +07:00
public void OnSceneLoadDone(NetworkRunner runner)
{
string currentSceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
2026-04-30 15:45:37 +07:00
if (runner.IsServer && currentSceneName == "Main Scene")
2026-04-29 13:10:00 +07:00
{
foreach (var player in runner.ActivePlayers)
{
2026-05-12 21:43:03 +07:00
SpawnPlayer(runner, player);
2026-04-29 13:10:00 +07:00
}
}
2026-04-30 15:45:37 +07:00
if (currentSceneName == "Main Scene")
{
2026-04-30 00:55:16 +07:00
UIManager.Instance?.OnGameStarted();
2026-04-30 15:45:37 +07:00
}
2026-04-29 13:10:00 +07:00
}
2026-04-30 15:45:37 +07:00
2026-04-28 19:04:09 +07:00
public void OnSceneLoadStart(NetworkRunner runner) { }
2026-04-23 08:56:35 +07:00
}
2026-04-11 18:13:40 +07:00
}