using System; using System.Threading; using BriarQueen.Data.Identifiers; using BriarQueen.Data.IO.Saves; using BriarQueen.Framework.Effects; using BriarQueen.Framework.Events.Save; using BriarQueen.Framework.Managers.Interaction; using BriarQueen.Framework.Managers.UI; using BriarQueen.Framework.Managers.UI.Base; using BriarQueen.Framework.Services.Puzzles.Base; using BriarQueen.Framework.Services.Tutorials; using BriarQueen.Game.Levels.ChapterOne.Ashwick; using Cysharp.Threading.Tasks; using MemoryPack; using PrimeTween; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using VContainer; namespace BriarQueen.Game.Puzzles.ChapterOne.AshwickHallow { [MemoryPackable] public partial class AshwickGateKeypadPuzzleState { public string Digits; } public class AshwickGateKeypadPuzzle : BasePuzzle, IPuzzleStateful, IUIWindow, IUIBackHandler, IUIOverlayHost { private const string CorrectCode = "312"; private const int RequiredDigits = 3; [Header("Scene References")] [SerializeField] private AshwickOutskirts _outskirts; [SerializeField] private CanvasGroup _panelGroup; [SerializeField] private TMP_InputField _displayField; [SerializeField] private UILightGlow _statusGlow; [SerializeField] private GraphicRaycaster _graphicRaycaster; [SerializeField] private Button _closeButton; [SerializeField] private Button[] _digitButtons = new Button[9]; [Header("Light Colors")] [SerializeField] private Color _incorrectGlowColor = new(0.85f, 0.2f, 0.2f, 1f); [SerializeField] private Color _correctGlowColor = new(0.2f, 0.85f, 0.35f, 1f); [Header("Timing")] [SerializeField] private float _feedbackDuration = 0.35f; [SerializeField] private TweenSettings _panelFadeTweenSettings = new() { duration = 1.5f, ease = Ease.OutQuad, useUnscaledTime = true }; private readonly UnityAction[] _digitCallbacks = new UnityAction[9]; private InteractManager _interactManager; private CancellationTokenSource _panelCts; private Sequence _panelSequence; private string _currentDigits = string.Empty; private bool _isCompleted; private bool _isEvaluating; private bool _isOpen; private bool _raycasterRegistered; private bool _skipSaveOnHide; private TutorialService _tutorialService; private UIManager _uiManager; public override string PuzzleID => PuzzleIdentifiers.AllPuzzles[PuzzleKey.AshwickMarketGate]; public bool IsCompleted => _isCompleted || SaveManager.GetLevelFlag(LevelFlag.AshwickGateOpen); public WindowType WindowType => WindowType.AshwickGateKeypadWindow; public UIPauseBehavior PauseBehavior => UIPauseBehavior.OpenPauseOverlay; [Inject] public void ConstructKeypad( InteractManager interactManager, UIManager uiManager, TutorialService tutorialService) { _interactManager = interactManager; _uiManager = uiManager; _tutorialService = tutorialService; } private void Awake() { if (_displayField != null) { _displayField.readOnly = true; _displayField.text = string.Empty; } BindButtons(); SetPanelState(0f, false, false); SyncDisplay(); } private void Start() { _uiManager?.RegisterWindow(this); } private void OnDestroy() { _uiManager?.UnregisterWindow(this); UnbindButtons(); TryUnregisterRaycaster(); CancelPanelTween(); } public override UniTask PostLoad() { _isCompleted = SaveManager.GetLevelFlag(LevelFlag.AshwickGateOpen); _isOpen = false; _isEvaluating = false; _skipSaveOnHide = false; return UniTask.CompletedTask; } public override UniTask CompletePuzzle() { return CompletePuzzleInternal(); } public void Open() { if (IsCompleted || _uiManager == null) return; _uiManager.OpenWindow(WindowType); } public void Close() { _uiManager?.CloseWindow(WindowType); } public UniTask CaptureState() { var state = new AshwickGateKeypadPuzzleState { Digits = IsCompleted ? string.Empty : _currentDigits }; return UniTask.FromResult(MemoryPackSerializer.Serialize(state)); } public UniTask RestoreState(byte[] state) { _isCompleted = SaveManager.GetLevelFlag(LevelFlag.AshwickGateOpen); _currentDigits = string.Empty; if (!_isCompleted && state is { Length: > 0 }) { var restored = MemoryPackSerializer.Deserialize(state); _currentDigits = restored?.Digits ?? string.Empty; if (_currentDigits.Length > RequiredDigits) _currentDigits = _currentDigits[..RequiredDigits]; } SyncDisplay(); return UniTask.CompletedTask; } private async UniTask CompletePuzzleInternal() { if (IsCompleted || _outskirts == null) return; _isCompleted = true; _currentDigits = string.Empty; SyncDisplay(); SaveManager.SetPuzzleCompleted(PuzzleKey.AshwickMarketGate, true, requestSave: false); _skipSaveOnHide = true; _uiManager?.CloseWindow(WindowType); AudioManager.Play(AudioNameIdentifiers.Get(SFXKey.AshwickGateOpening)); await _outskirts.OpenGate(); } public async UniTask Show() { if (IsCompleted || _isEvaluating || _isOpen) return; _isOpen = true; ResetPanelTween(); SetPanelState(0f, false, true); SyncDisplay(); EnsureExclusiveRaycaster(); try { _panelSequence = Sequence.Create(useUnscaledTime: true) .Group(Tween.Alpha(_panelGroup, new TweenSettings { startValue = 0f, endValue = 1f, settings = _panelFadeTweenSettings })); await _panelSequence.ToUniTask(cancellationToken: _panelCts.Token); } catch (OperationCanceledException) { return; } finally { _panelSequence = default; } SetPanelState(1f, true, true); _tutorialService?.DisplayTutorial(TutorialPopupID.LeavingPuzzles); } public async UniTask Hide() { if (!_isOpen) return; _isOpen = false; ResetPanelTween(); if (_panelGroup != null) { _panelGroup.interactable = false; _panelGroup.blocksRaycasts = true; } try { _panelSequence = Sequence.Create(useUnscaledTime: true) .Group(Tween.Alpha(_panelGroup, new TweenSettings { startValue = _panelGroup != null ? _panelGroup.alpha : 0f, endValue = 0f, settings = _panelFadeTweenSettings })); await _panelSequence.ToUniTask(cancellationToken: _panelCts.Token); } catch (OperationCanceledException) { return; } finally { _panelSequence = default; } SetPanelState(0f, false, false); TryUnregisterRaycaster(); if (!_skipSaveOnHide && !_isCompleted) EventCoordinator.PublishImmediate(new RequestGameSaveEvent()); _skipSaveOnHide = false; } public bool HandleBackRequest() { Close(); return true; } public bool CanSuspendFor(WindowType incomingWindowType) { return incomingWindowType == WindowType.PauseMenuWindow; } public async UniTask SuspendForOverlay() { if (!_isOpen) return; ResetPanelTween(); if (_panelGroup != null) { _panelGroup.interactable = false; _panelGroup.blocksRaycasts = false; } try { _panelSequence = Sequence.Create(useUnscaledTime: true) .Group(Tween.Alpha(_panelGroup, new TweenSettings { startValue = _panelGroup != null ? _panelGroup.alpha : 1f, endValue = 0f, settings = _panelFadeTweenSettings })); await _panelSequence.ToUniTask(cancellationToken: _panelCts.Token); } catch (OperationCanceledException) { return; } finally { _panelSequence = default; } SetPanelState(0f, false, false); } public async UniTask ResumeFromOverlay() { if (!_isOpen) return; ResetPanelTween(); EnsureExclusiveRaycaster(); SetPanelState(0f, false, true); try { _panelSequence = Sequence.Create(useUnscaledTime: true) .Group(Tween.Alpha(_panelGroup, new TweenSettings { startValue = 0f, endValue = 1f, settings = _panelFadeTweenSettings })); await _panelSequence.ToUniTask(cancellationToken: _panelCts.Token); } catch (OperationCanceledException) { return; } finally { _panelSequence = default; } SetPanelState(1f, !_isEvaluating, true); } private void OnDigitPressed(int digit) { if (_isEvaluating || IsCompleted || !_isOpen || _currentDigits.Length >= RequiredDigits) return; _currentDigits += digit.ToString(); SyncDisplay(); if (_currentDigits.Length == RequiredDigits) EvaluateCode().Forget(); } private async UniTaskVoid EvaluateCode() { _isEvaluating = true; if (_panelGroup != null) _panelGroup.interactable = false; if (_currentDigits == CorrectCode) { if (_statusGlow != null) { _statusGlow.SetLightColor(_correctGlowColor); await _statusGlow.TurnOn(); } await CompletePuzzleInternal(); } else { if (_statusGlow != null) { _statusGlow.SetLightColor(_incorrectGlowColor); await _statusGlow.TurnOn(); } _currentDigits = string.Empty; SyncDisplay(); if (_statusGlow != null) await _statusGlow.TurnOff(); if (_isOpen && _panelGroup != null) _panelGroup.interactable = true; } _isEvaluating = false; } private void BindButtons() { for (var i = 0; i < _digitButtons.Length; i++) { var button = _digitButtons[i]; if (button == null) continue; var digit = i + 1; _digitCallbacks[i] = () => OnDigitPressed(digit); button.onClick.AddListener(_digitCallbacks[i]); } if (_closeButton != null) _closeButton.onClick.AddListener(Close); } private void UnbindButtons() { for (var i = 0; i < _digitButtons.Length; i++) { var button = _digitButtons[i]; if (button == null || _digitCallbacks[i] == null) continue; button.onClick.RemoveListener(_digitCallbacks[i]); _digitCallbacks[i] = null; } if (_closeButton != null) _closeButton.onClick.RemoveListener(Close); } private void SyncDisplay() { if (_displayField == null) return; _displayField.text = _currentDigits; } private void SetPanelState(float alpha, bool interactable, bool blocksRaycasts) { if (_panelGroup == null) return; _panelGroup.alpha = alpha; _panelGroup.interactable = interactable; _panelGroup.blocksRaycasts = blocksRaycasts; } private void ResetPanelTween() { CancelPanelTween(); _panelCts = new CancellationTokenSource(); } private void CancelPanelTween() { if (_panelSequence.isAlive) _panelSequence.Stop(); if (_panelCts != null) { try { _panelCts.Cancel(); } catch { } _panelCts.Dispose(); _panelCts = null; } } private void EnsureExclusiveRaycaster() { if (_interactManager == null || _graphicRaycaster == null) return; if (!_raycasterRegistered) { _interactManager.AddUIRaycaster(_graphicRaycaster); _raycasterRegistered = true; } _interactManager.SetExclusiveRaycaster(_graphicRaycaster); } private void TryUnregisterRaycaster() { if (!_raycasterRegistered || _interactManager == null || _graphicRaycaster == null) return; _interactManager.RemoveUIRaycaster(_graphicRaycaster); _interactManager.ReleaseExclusiveRaycaster(_graphicRaycaster); _raycasterRegistered = false; } } }