// Assets/_Project/Scripts/Net/SessionFlow.cs
using Unity.Netcode;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace TD.Net
{
///
/// Bootstrap script — drop one on a GameObject in the MainMenu scene so it
/// runs once when the app boots. Subscribes to NGO disconnect callbacks
/// and routes the local peer back to the MainMenu scene whenever its
/// connection ends (host left, server kicked it, transport error, etc.).
///
///
/// Why this exists. Per Option A (see Project_Roadmap.md §1.7),
/// host disconnect tears down the session for everyone. Each client needs
/// to recover gracefully and return to its own main menu. Without this,
/// a client would be stuck in the Lobby or Match scene with no working
/// network connection after the host quits.
///
/// Lifecycle. The NetworkManager itself is marked
/// DontDestroyOnLoad by NGO once it spawns. This script is also marked
/// DontDestroyOnLoad so it survives scene transitions and its subscription
/// stays alive across MainMenu → Lobby → Match → back.
///
/// Server side. When the host (server) shuts down, the
/// OnServerStopped callback fires on the host too. For the host
/// that's fine — they've initiated the shutdown deliberately (e.g., from
/// the "Return to Main Menu" button) and being sent to MainMenu is what
/// they wanted.
///
public class SessionFlow : MonoBehaviour
{
// ----- Singleton --------------------------------------------------
public static SessionFlow Instance { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
private void Start()
{
HookCallbacks();
}
private void OnDestroy()
{
UnhookCallbacks();
if (Instance == this) Instance = null;
}
// ----- NGO callback wiring ---------------------------------------
private bool callbacksHooked;
private void HookCallbacks()
{
var nm = NetworkManager.Singleton;
if (nm == null)
{
// NetworkManager may not be alive yet on the very first frame
// depending on script execution order — retry next frame.
Invoke(nameof(HookCallbacks), 0.1f);
return;
}
if (callbacksHooked) return;
nm.OnClientDisconnectCallback += HandleClientDisconnect;
nm.OnServerStopped += HandleServerStopped;
callbacksHooked = true;
}
private void UnhookCallbacks()
{
if (!callbacksHooked) return;
var nm = NetworkManager.Singleton;
if (nm != null)
{
nm.OnClientDisconnectCallback -= HandleClientDisconnect;
nm.OnServerStopped -= HandleServerStopped;
}
callbacksHooked = false;
}
// ----- Disconnect handlers ---------------------------------------
// Fires when ANY client disconnects on the server, and when the LOCAL
// client gets disconnected on a client. We only care about the latter:
// when the local peer loses its connection (host left, transport error,
// explicit Shutdown call), route back to the main menu.
private void HandleClientDisconnect(ulong clientId)
{
var nm = NetworkManager.Singleton;
if (nm == null) return;
// On the server, this fires when other clients disconnect — ignore
// those, the server keeps running. We only act when the LOCAL
// client's connection ends.
if (clientId != nm.LocalClientId) return;
ReturnToMainMenu();
}
// Fires on the server when the server shuts down (including the host).
// The host's own client also gets HandleClientDisconnect — but
// OnServerStopped is the canonical "server is gone" signal and is
// safer to act on for the host path.
private void HandleServerStopped(bool isHost)
{
ReturnToMainMenu();
}
// ----- Scene transition ------------------------------------------
// Returns the local peer to the MainMenu scene if they're not already
// there. Uses Unity's local SceneManager (NOT NGO's networked scene
// manager) because by the time this fires the network connection is
// already gone.
private static void ReturnToMainMenu()
{
if (SceneManager.GetActiveScene().name == SceneNames.MainMenu)
return;
Debug.Log("[SessionFlow] Connection ended — returning to main menu.");
SceneManager.LoadScene(SceneNames.MainMenu);
}
}
}