using UnityEngine;
using UnityEngine.SceneManagement;
using Ashwild.Network;
using Ashwild.Player;
namespace Ashwild.UI
{
///
/// In-game UI manager. Every openable window is a panel on the shared stack
/// (): the pause menu, its sub-screens, and the inventory. "Paused" means
/// a non-inventory panel is open — that freezes the world (optionally) and raises
/// ; the inventory instead keeps the world running but
/// locks input and shows the cursor via .
///
/// One Escape closes whatever is open (inventory or a sub-panel), then opens pause when nothing
/// is. The inventory toggle key opens/closes the inventory through the same stack, so the two can
/// never fight. Ignored while dead.
///
[DisallowMultipleComponent]
public class GameUIManager : UIManager
{
#region Serialized Fields
[Header("Pause")]
[Tooltip("The pause menu panel opened by Escape.")]
[SerializeField] private UIPanel pausePanel;
[Header("Quit")]
[Tooltip("Scene loaded when leaving the session back to the main menu.")]
[SerializeField] private string menuSceneName = "MenuScene";
#endregion
#region State
///
/// The registered inventory panel (resolved by its kind), toggled by the inventory key.
///
private UIPanel inventoryPanel;
///
/// The registered construction menu panel (resolved by its kind), toggled by the build
/// hammer's right-click through .
///
private UIPanel buildPanel;
///
/// True while a panel that pauses the world is open — i.e. anything but the inventory, the
/// construction menu or the chest, all local windows the world keeps running under.
///
public bool IsPaused => Current != null
&& Current.Kind != PanelKind.Inventory
&& Current.Kind != PanelKind.Build
&& Current.Kind != PanelKind.Chest;
// Last bus values pushed, so we only fire on real transitions.
private bool lastPaused;
private bool lastInventoryOpen;
private bool lastChestOpen;
private bool lastBuildOpen;
#endregion
#region Unity Lifecycle
///
/// Subscribes to the inventory toggle input (Cancel/Escape is handled by the base).
///
protected override void OnEnable()
{
base.OnEnable();
PlayerEvents.InventoryTogglePressed += HandleInventoryToggle;
PlayerEvents.BuildMenuToggleRequested += HandleBuildMenuToggle;
}
///
/// Unsubscribes and makes sure we never leave the game stuck paused on scene unload.
///
protected override void OnDisable()
{
base.OnDisable();
PlayerEvents.InventoryTogglePressed -= HandleInventoryToggle;
PlayerEvents.BuildMenuToggleRequested -= HandleBuildMenuToggle;
if (lastPaused)
{
lastPaused = false;
PlayerEvents.RaiseGamePaused(false);
}
if (lastInventoryOpen)
{
lastInventoryOpen = false;
PlayerEvents.RaiseInventoryOpenChanged(false);
}
if (lastChestOpen)
{
lastChestOpen = false;
PlayerEvents.RaiseChestOpenChanged(false);
}
if (lastBuildOpen)
{
lastBuildOpen = false;
PlayerEvents.RaiseBuildMenuOpenChanged(false);
}
}
///
/// Resolves the inventory panel and starts unpaused with the cursor locked.
///
protected override void Start()
{
base.Start();
ResolvePanelsByKind();
ApplyUIState(force: true);
}
#endregion
#region Escape Policy
///
/// Escape: close whatever is open (inventory or sub-panel), otherwise open the pause menu.
///
protected override void OnEscape()
{
if (PlayerEvents.IsDead) return;
if (HasOpenPanel)
{
if (CanGoBack) Back();
else CloseAll();
return;
}
OpenPause();
}
#endregion
#region Public API
///
/// Opens the pause menu (no-op if a panel is already open).
///
public void OpenPause()
{
if (pausePanel == null)
{
Debug.LogError("[GameUIManager] No pause panel assigned.", this);
return;
}
OpenPanel(pausePanel);
}
///
/// Closes every panel and returns to gameplay. Hook this to the pause menu "Resume" button.
///
public void Resume() => CloseAll();
///
/// Opens the Steam overlay to invite a friend to the current session.
///
public void InviteFriend()
{
if (SteamInviteService.Instance == null)
{
Debug.LogError("[GameUIManager] SteamInviteService introuvable — ajoute-le sur le NetworkManager.", this);
return;
}
SteamInviteService.Instance.OpenInviteOverlay();
}
///
/// Leaves the session and returns to the main menu scene behind the loading curtain. The
/// transition is owned by the persistent NetworkSessionManager (it survives the scene swap,
/// this manager does not); falls back to a direct, uncovered load only if it is missing.
///
public void QuitToMenu()
{
if (NetworkSessionManager.Instance != null)
{
NetworkSessionManager.Instance.ReturnToMenu(menuSceneName);
return;
}
Debug.LogWarning("[GameUIManager] NetworkSessionManager missing — loading menu directly without the loading curtain.", this);
SceneManager.LoadScene(menuSceneName);
}
#endregion
#region Inventory
///
/// Inventory key: close the inventory if it is open, otherwise open it (never over a pause menu).
///
private void HandleInventoryToggle()
{
if (PlayerEvents.IsDead) return;
if (Current != null && Current.Kind == PanelKind.Inventory)
{
CloseAll();
return;
}
// Don't open the inventory on top of the pause menu, the construction menu or a chest.
if (IsPaused) return;
if (Current != null && (Current.Kind == PanelKind.Build || Current.Kind == PanelKind.Chest)) return;
if (inventoryPanel != null)
OpenPanel(inventoryPanel);
}
///
/// Build hammer right-click: close the construction menu if it is open, otherwise open it
/// (never over the pause menu or the inventory). Mirrors the inventory toggle so the two
/// windows can never fight for the stack.
///
private void HandleBuildMenuToggle()
{
if (PlayerEvents.IsDead) return;
if (Current != null && Current.Kind == PanelKind.Build)
{
CloseAll();
return;
}
if (IsPaused) return;
if (Current != null && (Current.Kind == PanelKind.Inventory || Current.Kind == PanelKind.Chest)) return;
if (buildPanel != null)
OpenPanel(buildPanel);
}
///
/// Caches the registered inventory and construction-menu panels by their kind, so the
/// toggle handlers can open them without a hard type reference.
///
private void ResolvePanelsByKind()
{
if (panels == null) return;
foreach (UIPanel p in panels)
{
if (p == null) continue;
if (p.Kind == PanelKind.Inventory) inventoryPanel = p;
else if (p.Kind == PanelKind.Build) buildPanel = p;
}
}
#endregion
#region UI State
///
/// Re-applies cursor, time freeze and the pause/inventory bus states whenever the stack changes.
///
protected override void OnCurrentChanged() => ApplyUIState();
///
/// Drives the side effects of the open panel: cursor for any panel, world freeze + GamePaused
/// for pausing panels, and the InventoryOpenChanged / ChestOpenChanged / BuildMenuOpenChanged
/// bus states for the three local windows. The inventory, the chest and the construction menu
/// are windows the world keeps running under, so only "real" menus pause. All four bus states
/// feed PlayerEvents.InputLocked, so player control is cut whenever a panel is open.
///
private void ApplyUIState(bool force = false)
{
UIPanel cur = Current;
bool anyOpen = cur != null;
bool inventory = anyOpen && cur.Kind == PanelKind.Inventory;
bool chest = anyOpen && cur.Kind == PanelKind.Chest;
bool build = anyOpen && cur.Kind == PanelKind.Build;
bool paused = anyOpen && !inventory && !chest && !build;
Cursor.lockState = anyOpen ? CursorLockMode.None : CursorLockMode.Locked;
Cursor.visible = anyOpen;
if (force || paused != lastPaused)
{
lastPaused = paused;
PlayerEvents.RaiseGamePaused(paused);
}
if (force || inventory != lastInventoryOpen)
{
lastInventoryOpen = inventory;
PlayerEvents.RaiseInventoryOpenChanged(inventory);
}
if (force || chest != lastChestOpen)
{
lastChestOpen = chest;
PlayerEvents.RaiseChestOpenChanged(chest);
}
if (force || build != lastBuildOpen)
{
lastBuildOpen = build;
PlayerEvents.RaiseBuildMenuOpenChanged(build);
}
}
#endregion
}
}