306 lines
11 KiB
C#
306 lines
11 KiB
C#
using UnityEngine;
|
|
using Ashwild.Network;
|
|
using Ashwild.Player;
|
|
|
|
namespace Ashwild.UI
|
|
{
|
|
/// <summary>
|
|
/// In-game UI manager. Every openable window is a panel on the shared stack
|
|
/// (<see cref="UIManager"/>): the pause menu, its sub-screens, and the inventory. "Paused" means
|
|
/// a non-inventory panel is open — that freezes the world (optionally) and raises
|
|
/// <see cref="PlayerEvents.RaiseGamePaused"/>; the inventory instead keeps the world running but
|
|
/// locks input and shows the cursor via <see cref="PlayerEvents.RaiseInventoryOpenChanged"/>.
|
|
///
|
|
/// 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.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
public class GameUIManager : UIManager
|
|
{
|
|
#region Serialized Fields
|
|
|
|
[Header("Pause")]
|
|
[Tooltip("The pause menu panel opened by Escape.")]
|
|
[SerializeField] private UIPanel pausePanel;
|
|
|
|
#endregion
|
|
|
|
#region State
|
|
|
|
/// <summary>
|
|
/// The registered inventory panel (resolved by its kind), toggled by the inventory key.
|
|
/// </summary>
|
|
private UIPanel inventoryPanel;
|
|
|
|
/// <summary>
|
|
/// The registered construction menu panel (resolved by its kind), toggled by the build
|
|
/// hammer's right-click through <see cref="PlayerEvents.BuildMenuToggleRequested"/>.
|
|
/// </summary>
|
|
private UIPanel buildPanel;
|
|
|
|
/// <summary>
|
|
/// True while a panel that pauses the world is open — i.e. anything but the inventory (which a
|
|
/// chest reuses) or the construction menu, both local windows the world keeps running under.
|
|
/// </summary>
|
|
public bool IsPaused => Current != null
|
|
&& Current.Kind != PanelKind.Inventory
|
|
&& Current.Kind != PanelKind.Build;
|
|
|
|
// Last bus values pushed, so we only fire on real transitions.
|
|
private bool lastPaused;
|
|
private bool lastInventoryOpen;
|
|
private bool lastBuildOpen;
|
|
|
|
#endregion
|
|
|
|
#region Unity Lifecycle
|
|
|
|
/// <summary>
|
|
/// Subscribes to the inventory toggle input (Cancel/Escape is handled by the base).
|
|
/// </summary>
|
|
protected override void OnEnable()
|
|
{
|
|
base.OnEnable();
|
|
PlayerEvents.InventoryTogglePressed += HandleInventoryToggle;
|
|
PlayerEvents.BuildMenuToggleRequested += HandleBuildMenuToggle;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribes and makes sure we never leave the game stuck paused on scene unload.
|
|
/// </summary>
|
|
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 (lastBuildOpen)
|
|
{
|
|
lastBuildOpen = false;
|
|
PlayerEvents.RaiseBuildMenuOpenChanged(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the inventory panel and starts unpaused with the cursor locked.
|
|
/// </summary>
|
|
protected override void Start()
|
|
{
|
|
base.Start();
|
|
ResolvePanelsByKind();
|
|
ApplyUIState(force: true);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Escape Policy
|
|
|
|
/// <summary>
|
|
/// Escape, from the most local context outward: close whatever panel is open (inventory or
|
|
/// sub-panel), else back out of an in-world build placement, else open the pause menu.
|
|
///
|
|
/// The placement step matters because a ghost being positioned is a mode with no panel behind it:
|
|
/// the construction menu closes as soon as a card is picked, so HasOpenPanel is already false and
|
|
/// Escape used to fall straight through to the pause menu — leaving the player still holding a
|
|
/// ghost. It is routed through BuildCancelRequested rather than reaching for BuildManager, so
|
|
/// ending a placement stays the bus contract the hammer's right-click tap already uses.
|
|
///
|
|
/// Demolition mode is deliberately not handled here: it lasts only while right-click is held, so
|
|
/// it ends on release, and cancelling it from the outside would leave the hammer thinking it is
|
|
/// still held.
|
|
/// </summary>
|
|
protected override void OnEscape()
|
|
{
|
|
if (PlayerEvents.IsDead) return;
|
|
|
|
if (HasOpenPanel)
|
|
{
|
|
if (CanGoBack) Back();
|
|
else CloseAll();
|
|
return;
|
|
}
|
|
|
|
if (PlayerEvents.IsPlacingBuild)
|
|
{
|
|
PlayerEvents.RaiseBuildCancelRequested();
|
|
return;
|
|
}
|
|
|
|
OpenPause();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public API
|
|
|
|
/// <summary>
|
|
/// Opens the pause menu (no-op if a panel is already open).
|
|
/// </summary>
|
|
public void OpenPause()
|
|
{
|
|
if (pausePanel == null)
|
|
{
|
|
Debug.LogError("[GameUIManager] No pause panel assigned.", this);
|
|
return;
|
|
}
|
|
OpenPanel(pausePanel);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes every panel and returns to gameplay. Hook this to the pause menu "Resume" button.
|
|
/// </summary>
|
|
public void Resume() => CloseAll();
|
|
|
|
/// <summary>
|
|
/// Opens the Steam overlay to invite a friend to the current session.
|
|
/// </summary>
|
|
public void InviteFriend()
|
|
{
|
|
if (SteamInviteService.Instance == null)
|
|
{
|
|
Debug.LogError("[GameUIManager] SteamInviteService introuvable — ajoute-le sur le NetworkManager.", this);
|
|
return;
|
|
}
|
|
SteamInviteService.Instance.OpenInviteOverlay();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Leaves the session and returns to the main menu scene behind the loading curtain. The
|
|
/// transition — and the menu scene name — is owned by the persistent NetworkSessionManager (it
|
|
/// survives the scene swap and is the single source of truth for scene names; this manager does
|
|
/// neither). Logs and no-ops if it is somehow missing rather than tearing down uncovered.
|
|
/// </summary>
|
|
public void QuitToMenu()
|
|
{
|
|
if (NetworkSessionManager.Instance == null)
|
|
{
|
|
Debug.LogError("[GameUIManager] NetworkSessionManager missing — cannot return to menu.", this);
|
|
return;
|
|
}
|
|
|
|
NetworkSessionManager.Instance.ReturnToMenu();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Inventory
|
|
|
|
/// <summary>
|
|
/// Inventory key: close the inventory if it is open, otherwise open it (never over a pause menu).
|
|
/// </summary>
|
|
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 or the construction menu.
|
|
if (IsPaused) return;
|
|
if (Current != null && Current.Kind == PanelKind.Build) return;
|
|
|
|
if (inventoryPanel != null)
|
|
OpenPanel(inventoryPanel);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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) return;
|
|
|
|
if (buildPanel != null)
|
|
OpenPanel(buildPanel);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Caches the registered inventory and construction-menu panels by their kind, so the
|
|
/// toggle handlers can open them without a hard type reference.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Re-applies cursor, time freeze and the pause/inventory bus states whenever the stack changes.
|
|
/// </summary>
|
|
protected override void OnCurrentChanged() => ApplyUIState();
|
|
|
|
/// <summary>
|
|
/// Drives the side effects of the open panel: cursor for any panel, world freeze + GamePaused
|
|
/// for pausing panels, and the InventoryOpenChanged / BuildMenuOpenChanged bus states for the
|
|
/// two local windows. The inventory (which a chest reuses) and the construction menu are windows
|
|
/// the world keeps running under, so only "real" menus pause. The ChestOpenChanged sub-state is
|
|
/// owned by the inventory window itself, since a chest is just the inventory panel in chest mode.
|
|
/// All these bus states feed PlayerEvents.InputLocked, so player control is cut whenever a panel
|
|
/// is open.
|
|
/// </summary>
|
|
private void ApplyUIState(bool force = false)
|
|
{
|
|
UIPanel cur = Current;
|
|
bool anyOpen = cur != null;
|
|
bool inventory = anyOpen && cur.Kind == PanelKind.Inventory;
|
|
bool build = anyOpen && cur.Kind == PanelKind.Build;
|
|
bool paused = anyOpen && !inventory && !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 || build != lastBuildOpen)
|
|
{
|
|
lastBuildOpen = build;
|
|
PlayerEvents.RaiseBuildMenuOpenChanged(build);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|