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; /// /// True while a non-inventory panel is open — i.e. the game is paused. /// public bool IsPaused => Current != null && Current.Kind != PanelKind.Inventory; // Last bus values pushed, so we only fire on real transitions. private bool lastPaused; private bool lastInventoryOpen; #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; } /// /// Unsubscribes and makes sure we never leave the game stuck paused on scene unload. /// protected override void OnDisable() { base.OnDisable(); PlayerEvents.InventoryTogglePressed -= HandleInventoryToggle; if (lastPaused) { lastPaused = false; PlayerEvents.RaiseGamePaused(false); } if (lastInventoryOpen) { lastInventoryOpen = false; PlayerEvents.RaiseInventoryOpenChanged(false); } } /// /// Resolves the inventory panel and starts unpaused with the cursor locked. /// protected override void Start() { base.Start(); ResolveInventoryPanel(); 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. /// public void QuitToMenu() { if (NetworkSessionManager.Instance != null) NetworkSessionManager.Instance.StopSession(); 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. if (IsPaused) return; if (inventoryPanel != null) OpenPanel(inventoryPanel); } /// /// Finds the registered panel flagged as the inventory. /// private void ResolveInventoryPanel() { if (panels == null) return; foreach (UIPanel p in panels) { if (p != null && p.Kind == PanelKind.Inventory) { inventoryPanel = p; return; } } } #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 non-inventory panels, and InventoryOpenChanged for the inventory. Both 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 paused = anyOpen && !inventory; 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); } } #endregion } }