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 } }