using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
///
/// The Esc overview menu (the "Overview UI" canvas in Main). Owns ALL Escape handling (Tab acts as an
/// alternative to Esc): Esc toggles
/// this panel as a modal pause overlay (gameplay suspended via );
/// while the session browser or match results overlay is open instead, Esc closes that, and while paint
/// mode is active Esc exits to the camera mode (Player Control, or First Person for the hunter). Buttons
/// switch game modes (via ) or open the session browser /
/// match results overlay, and every click closes the panel. Mode buttons are only shown for modes present in the current role-scoped set
/// (); the active mode's button is disabled. Attach to the
/// always-active canvas root; is the toggled visual child.
///
public class OverviewMenu : MonoBehaviour
{
[Tooltip("Mode manager driving pause + mode switches. Empty = found automatically.")]
[SerializeField] private GameModeManager gameMode;
[Tooltip("Session browser binder. While its overlay is open it owns Esc (close-only) and this menu " +
"stays closed. Empty = found automatically.")]
[SerializeField] private OnlineSessionBrowserUI sessionBrowser;
[Tooltip("Match results screen binder. While its overlay is open it owns Esc (close-only) and this " +
"menu stays closed. Empty = found automatically.")]
[SerializeField] private MatchResultScreen matchResultScreen;
[Header("Panel")]
[Tooltip("Visual container toggled by Esc. Must be a CHILD of this binder's object so the binder " +
"keeps reading Esc while the panel is hidden.")]
[SerializeField] private GameObject panelRoot;
[Header("Buttons")]
[SerializeField] private Button sessionButton;
[SerializeField] private Button matchResultButton;
[Tooltip("Enters Player Control (third-person orbit) mode.")]
[SerializeField] private Button playerControlButton;
[SerializeField] private Button paintButton;
[SerializeField] private Button firstPersonButton;
[SerializeField] private Button freeCamButton;
[Tooltip("Triggers the hider's manual radar ping (same as the E key) and closes the menu. Only shown " +
"for a hider during Seek.")]
[SerializeField] private Button tauntButton;
private bool _open;
private void Awake()
{
if (!gameMode) gameMode = FindFirstObjectByType();
if (!sessionBrowser) sessionBrowser = FindFirstObjectByType();
if (!matchResultScreen) matchResultScreen = FindFirstObjectByType();
if (panelRoot) panelRoot.SetActive(false);
}
private void OnEnable()
{
if (sessionButton) sessionButton.onClick.AddListener(OnSession);
if (matchResultButton) matchResultButton.onClick.AddListener(OnMatchResult);
if (playerControlButton) playerControlButton.onClick.AddListener(OnPlayerControl);
if (paintButton) paintButton.onClick.AddListener(OnPaint);
if (firstPersonButton) firstPersonButton.onClick.AddListener(OnFirstPerson);
if (freeCamButton) freeCamButton.onClick.AddListener(OnFreeCam);
if (tauntButton) tauntButton.onClick.AddListener(OnTaunt);
}
private void OnDisable()
{
if (sessionButton) sessionButton.onClick.RemoveListener(OnSession);
if (matchResultButton) matchResultButton.onClick.RemoveListener(OnMatchResult);
if (playerControlButton) playerControlButton.onClick.RemoveListener(OnPlayerControl);
if (paintButton) paintButton.onClick.RemoveListener(OnPaint);
if (firstPersonButton) firstPersonButton.onClick.RemoveListener(OnFirstPerson);
if (freeCamButton) freeCamButton.onClick.RemoveListener(OnFreeCam);
if (tauntButton) tauntButton.onClick.RemoveListener(OnTaunt);
}
private void Update()
{
bool browserOpen = sessionBrowser && sessionBrowser.IsOpen;
bool resultsOpen = matchResultScreen && matchResultScreen.IsOpen;
if (_open)
{
// Another overlay took over (results Resolution auto-open) -> hide this panel; the pause
// is that overlay's now, so don't release it.
if (browserOpen || resultsOpen)
HidePanel();
// An external state reset (respawn/bind, disconnect) dropped the pause underneath the
// open panel -> don't linger as a stale overlay over live gameplay.
else if (gameMode && !gameMode.IsPaused)
HidePanel();
}
if (!EscapePressed()) return;
if (browserOpen)
sessionBrowser.Close();
else if (resultsOpen)
matchResultScreen.Close();
else if (_open)
Close(resume: true);
// Paint mode: Esc exits back to the camera mode instead of opening the menu. Player Control
// first; the hunter's role-scoped set has none, so it falls through to First Person.
else if (gameMode && gameMode.EffectiveMode is PaintMode)
{
if (!gameMode.TrySwitchTo() && !gameMode.TrySwitchTo())
Open();
}
// No active/paused mode = the inert no-player state (pre-lobby Main, where the session panel
// is always shown) - nothing to pause, don't open.
else if (gameMode && gameMode.EffectiveMode != null)
Open();
}
private void Open()
{
_open = true;
if (panelRoot) panelRoot.SetActive(true);
RefreshButtons();
if (gameMode) gameMode.SetPaused(true);
}
/// Hides the panel; = false hands the pause over (session
/// browser opening) instead of resuming gameplay.
private void Close(bool resume)
{
HidePanel();
if (resume && gameMode) gameMode.SetPaused(false);
}
private void HidePanel()
{
_open = false;
if (panelRoot) panelRoot.SetActive(false);
}
private void OnSession()
{
Close(resume: false);
// Not connected (defensive - the menu should not be openable then): don't strand the pause.
if (!(sessionBrowser && sessionBrowser.Open()) && gameMode)
gameMode.SetPaused(false);
}
private void OnMatchResult()
{
Close(resume: false);
if (!(matchResultScreen && matchResultScreen.Open()) && gameMode)
gameMode.SetPaused(false);
}
private void OnTaunt()
{
Close(resume: true);
if (MatchManager.Instance) MatchManager.Instance.RequestManualPing();
}
private void OnPlayerControl() => SwitchMode();
private void OnPaint() => SwitchMode();
private void OnFirstPerson() => SwitchMode();
private void OnFreeCam() => SwitchMode();
private void SwitchMode() where T : GameMode
{
// Resume first so the switch runs on a live cycle (SwitchTo on a paused manager would fight
// the pause bookkeeping); picking the already-active mode is a no-op inside SwitchTo.
Close(resume: true);
if (gameMode) gameMode.TrySwitchTo();
}
/// Shows only the buttons whose mode exists in the current role-scoped set and disables
/// the active mode's button. Ran on every open - the set only changes with a (re)bind, which
/// force-closes the panel anyway.
private void RefreshButtons()
{
if (!gameMode) return;
IReadOnlyList modes = gameMode.Modes;
GameMode active = gameMode.EffectiveMode;
// Match results only exist from the first Hide phase on - hide the button until then
// (same pattern as the role-scoped mode buttons).
if (matchResultButton)
matchResultButton.gameObject.SetActive(matchResultScreen && matchResultScreen.HasResults);
// Taunt/ping only does something for a hider during Seek (the server re-gates found hiders) -
// hide it otherwise, same pattern as the role-scoped mode buttons.
if (tauntButton)
tauntButton.gameObject.SetActive(gameMode.LocalPlayerIsHider && MatchManager.Instance &&
MatchManager.Instance.LocalSubPhase == MatchManager.MatchSubPhase.Seek);
SetModeButton(playerControlButton, modes, active);
SetModeButton(paintButton, modes, active);
SetModeButton(firstPersonButton, modes, active);
SetModeButton(freeCamButton, modes, active);
}
private static void SetModeButton(Button button, IReadOnlyList modes, GameMode active)
where T : GameMode
{
if (!button) return;
bool present = false;
for (int i = 0; i < modes.Count; i++)
if (modes[i] is T) { present = true; break; }
button.gameObject.SetActive(present);
button.interactable = !(active is T);
}
// Esc is NOT TypingGuard-gated: it must work even while a text field has focus.
// Tab (alternative binding) IS gated so it stays usable for typing/field navigation.
private static bool EscapePressed()
{
#if ENABLE_INPUT_SYSTEM
return InputUtil.KeyPressedThisFrame(Key.Escape) || InputUtil.GameplayKeyPressed(Key.Tab);
#else
return InputUtil.KeyPressedThisFrame(KeyCode.Escape) || InputUtil.GameplayKeyPressed(KeyCode.Tab);
#endif
}
}