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.Services.Puzzles.Base; 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 { 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; public override string PuzzleID => PuzzleIdentifiers.AllPuzzles[PuzzleKey.AshwickMarketGate]; public bool IsCompleted => _isCompleted || SaveManager.GetLevelFlag(LevelFlag.AshwickGateOpen); [Inject] public void ConstructKeypad(InteractManager interactManager) { _interactManager = interactManager; } private void Awake() { if (_displayField != null) { _displayField.readOnly = true; _displayField.text = string.Empty; } BindButtons(); SetPanelState(0f, false, false); SyncDisplay(); } private void OnDestroy() { UnbindButtons(); TryUnregisterRaycaster(); CancelPanelTween(); } public override UniTask PostLoad() { _isCompleted = SaveManager.GetLevelFlag(LevelFlag.AshwickGateOpen); _isOpen = false; _isEvaluating = false; SetPanelState(0f, false, false); SyncDisplay(); _statusGlow?.TurnOff().Forget(); return UniTask.CompletedTask; } public override UniTask CompletePuzzle() { return CompletePuzzleInternal(); } public void Open() { OpenInternal().Forget(); } public void Close() { CloseInternal(requestSave: true).Forget(); } 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); await CloseInternal(requestSave: false); await _outskirts.OpenGate(); } private async UniTaskVoid OpenInternal() { if (IsCompleted || _isEvaluating || _isOpen) return; _isOpen = true; ResetPanelTween(); SetPanelState(0f, false, true); SyncDisplay(); TryRegisterRaycaster(); _statusGlow?.TurnOff().Forget(); 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); } private async UniTask CloseInternal(bool requestSave) { 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 (requestSave) EventCoordinator.PublishImmediate(new RequestGameSaveEvent()); } 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 TryRegisterRaycaster() { if (_raycasterRegistered || _interactManager == null || _graphicRaycaster == null) return; _interactManager.AddUIRaycaster(_graphicRaycaster); _interactManager.SetExclusiveRaycaster(_graphicRaycaster); _raycasterRegistered = true; } private void TryUnregisterRaycaster() { if (!_raycasterRegistered || _interactManager == null || _graphicRaycaster == null) return; _interactManager.RemoveUIRaycaster(_graphicRaycaster); _interactManager.ClearExclusiveRaycaster(); _raycasterRegistered = false; } } }