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-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-04-28 19:04:09 +07:00
|
|
|
runner.Despawn(networkObject);
|
|
|
|
|
_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)
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
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
|
|
|
}
|