using System.Threading; using BriarQueen.Framework.Managers.Levels.Data; using BriarQueen.Framework.Managers.Player.Data; using Cysharp.Threading.Tasks; using PrimeTween; using UnityEngine; namespace BriarQueen.Game.Puzzles.ChapterOne.LaxleyHouse.Clock { public class LaxleyClockHand : BaseItem { [Header("State")] [SerializeField] private bool _placed; [SerializeField] [Range(0, 11)] private int _currentRotationStep; [SerializeField] private bool _locked; [SerializeField] private bool _isRotating; [SerializeField] private bool _canRotate; [Header("Rotation")] [SerializeField] private float[] _rotationSteps = new float[12] { 0f, // 12 -30f, // 1 -60f, // 2 -90f, // 3 -120f, // 4 -150f, // 5 -180f, // 6 -210f, // 7 -240f, // 8 -270f, // 9 -300f, // 10 -330f // 11 }; [SerializeField] private float _rotateDuration = 0.15f; [SerializeField] private Ease _rotateEase = Ease.Linear; [Header("Placement")] [SerializeField] private float _placeDuration = 0.2f; [SerializeField] private Ease _placeEase = Ease.OutSine; [Header("Components")] [SerializeField] private GameObject _parentPivot; [SerializeField] private CanvasGroup _parentCanvasGroup; [Header("Placement Behaviour")] [SerializeField] private bool _randomiseRotationOnFirstPlace = true; private Sequence _placeSequence; private Sequence _rotateSequence; private CancellationTokenSource _placeCTS; private CancellationTokenSource _rotateCTS; private LaxleyClockFace _clockFace; public bool Placed => _placed; public int CurrentRotationStep => _currentRotationStep; public void Initialise(LaxleyClockFace clockFace, LaxleyClockBasePuzzle owningPuzzle) { _clockFace = clockFace; _canRotate = false; if (_placed) { if (_parentPivot != null) _parentPivot.SetActive(true); if (_parentCanvasGroup != null) _parentCanvasGroup.alpha = 1f; ApplyRotationImmediate(_currentRotationStep); } else { if (_parentPivot != null) _parentPivot.SetActive(false); if (_parentCanvasGroup != null) _parentCanvasGroup.alpha = 0f; } RefreshVisualState(); } public override async UniTask OnInteract(ItemDataSo item = null) { if (!IsInteractable()) return; await RotateToNextStep(); await _clockFace.NotifyHandChanged(); } public async UniTask Place() { if (_placed) return; _placed = true; _locked = false; _canRotate = false; CancelPlaceTween(); _placeCTS = new CancellationTokenSource(); if (_randomiseRotationOnFirstPlace) _currentRotationStep = Random.Range(0, _rotationSteps.Length); if (_parentPivot != null) _parentPivot.SetActive(true); ApplyRotationImmediate(_currentRotationStep); if (_parentCanvasGroup != null) { _parentCanvasGroup.alpha = 0f; _parentCanvasGroup.blocksRaycasts = false; _parentCanvasGroup.interactable = false; } _placeSequence.Stop(); _placeSequence = Sequence.Create() .Group(Tween.Alpha(_parentCanvasGroup, 1f, _placeDuration, _placeEase)); try { await _placeSequence.ToUniTask(cancellationToken: _placeCTS.Token); } catch { } RefreshVisualState(); } public void RestoreState(bool placed, int rotationStep, bool locked) { CancelPlaceTween(); CancelRotateTween(); _placed = placed; _locked = locked; _isRotating = false; _canRotate = false; _currentRotationStep = Mathf.Clamp(rotationStep, 0, 11); if (_parentPivot != null) _parentPivot.SetActive(_placed); if (_placed) { ApplyRotationImmediate(_currentRotationStep); if (_parentCanvasGroup != null) _parentCanvasGroup.alpha = 1f; } else { if (_parentCanvasGroup != null) _parentCanvasGroup.alpha = 0f; } RefreshVisualState(); } public void SetCanRotate(bool canRotate) { _canRotate = canRotate; RefreshVisualState(); } public void Lock() { _locked = true; _canRotate = false; RefreshVisualState(); } public void Hide() { CancelPlaceTween(); CancelRotateTween(); _canRotate = false; _isRotating = false; if (_parentCanvasGroup != null) { _parentCanvasGroup.blocksRaycasts = false; _parentCanvasGroup.interactable = false; _parentCanvasGroup.alpha = 0f; } if (_parentPivot != null) _parentPivot.SetActive(false); } private async UniTask RotateToNextStep() { if (!IsInteractable()) return; _isRotating = true; RefreshVisualState(); CancelRotateTween(); _rotateCTS = new CancellationTokenSource(); _currentRotationStep = (_currentRotationStep + 1) % 12; Vector3 targetEuler = _parentPivot.transform.localEulerAngles; targetEuler.z = _rotationSteps[_currentRotationStep]; _rotateSequence.Stop(); _rotateSequence = Sequence.Create() .Group( Tween.LocalRotation( _parentPivot.transform, Quaternion.Euler(targetEuler), _rotateDuration, _rotateEase)); try { await _rotateSequence.ToUniTask(cancellationToken: _rotateCTS.Token); } catch { } _isRotating = false; RefreshVisualState(); } private void ApplyRotationImmediate(int step) { if (_parentPivot == null) return; Vector3 euler = _parentPivot.transform.localEulerAngles; euler.z = _rotationSteps[Mathf.Clamp(step, 0, 11)]; _parentPivot.transform.localEulerAngles = euler; } private void RefreshVisualState() { bool interactable = IsInteractable(); if (_parentCanvasGroup != null) { _parentCanvasGroup.blocksRaycasts = interactable; _parentCanvasGroup.interactable = interactable; } } private bool IsInteractable() { return _placed && !_locked && !_isRotating && _canRotate; } private void CancelPlaceTween() { _placeSequence.Stop(); if (_placeCTS != null) { _placeCTS.Cancel(); _placeCTS.Dispose(); _placeCTS = null; } } private void CancelRotateTween() { _rotateSequence.Stop(); if (_rotateCTS != null) { _rotateCTS.Cancel(); _rotateCTS.Dispose(); _rotateCTS = null; } } private void OnDestroy() { CancelPlaceTween(); CancelRotateTween(); } } }