using System.Collections; using Core; using Core.Lifecycle; using Core.SaveLoad; using Input; using Pixelplacement; using UI.Core; using UnityEngine; using UnityEngine.Audio; namespace UI.Tutorial { public class DivingTutorial : ManagedBehaviour, ITouchInputConsumer { public enum ProgressType { Manual, // Wait for player tap after animation loop Auto // Automatically progress after animation loop } private StateMachine _stateMachine; public bool playTutorial; public AudioSource bottleAudioPlayer; public AudioResource introVO; [SerializeField] private ProgressType progressType = ProgressType.Auto; // gating for input until current state's animation finishes first loop [SerializeField] private GameObject tapPrompt; private bool _canAcceptInput; private Coroutine _waitLoopCoroutine; public override int ManagedAwakePriority => 200; // Tutorial runs late, after other systems internal override void OnManagedStart() { // Ensure prompt is hidden initially (even before tutorial initialization) if (tapPrompt != null) tapPrompt.SetActive(false); if (playTutorial && !SaveLoadManager.Instance.currentSaveData.playedDivingTutorial) { // TODO: Possibly do it better, but for now just mark tutorial as played immediately SaveLoadManager.Instance.currentSaveData.playedDivingTutorial = true; // pause the game, hide UI, and register for input overrides GameManager.Instance.RequestPause(this); UIPageController.Instance.HideAllUI(); InputManager.Instance.RegisterOverrideConsumer(this); // Setup references _stateMachine = GetComponentInChildren(); _stateMachine.OnLastStateExited.AddListener(RemoveTutorial); // prepare gating for the initial active state SetupInputGateForCurrentState(); } else { RemoveTutorial(); } } void RemoveTutorial() { Logging.Debug("Remove me!"); if (_waitLoopCoroutine != null) { StopCoroutine(_waitLoopCoroutine); _waitLoopCoroutine = null; } // Unpause, unregister input, and show UI InputManager.Instance.UnregisterOverrideConsumer(this); UIPageController.Instance.ShowAllUI(); GameManager.Instance.ReleasePause(this); // hide prompt if present if (tapPrompt != null) tapPrompt.SetActive(false); Destroy(gameObject); bottleAudioPlayer.resource = introVO; bottleAudioPlayer.Play(); } public void OnTap(Vector2 position) { if (!_canAcceptInput) return; // block taps until allowed // consume this tap and immediately block further taps SetInputEnabled(false); // move to next state _stateMachine.Next(true); // after the state changes, set up gating for the new active state's animation SetupInputGateForCurrentState(); } public void OnHoldStart(Vector2 position) { } public void OnHoldMove(Vector2 position) { } public void OnHoldEnd(Vector2 position) { } // centralize enabling/disabling input and the tap prompt private void SetInputEnabled(bool allow) { _canAcceptInput = allow; if (tapPrompt != null) { // Only show tap prompt in Manual mode tapPrompt.SetActive(allow && progressType == ProgressType.Manual); } } private void SetupInputGateForCurrentState() { if (_waitLoopCoroutine != null) { StopCoroutine(_waitLoopCoroutine); _waitLoopCoroutine = null; } _waitLoopCoroutine = StartCoroutine(WaitForFirstLoopOnActiveState()); } private IEnumerator WaitForFirstLoopOnActiveState() { // wait a frame to ensure StateMachine has activated the correct state GameObject yield return null; // find the active child under the StateMachine (the current state) Transform smTransform = _stateMachine != null ? _stateMachine.transform : transform; Transform activeState = null; for (int i = 0; i < smTransform.childCount; i++) { var child = smTransform.GetChild(i); if (child.gameObject.activeInHierarchy) { activeState = child; break; } } if (activeState == null) { // if we can't find an active state, fail open: allow input SetInputEnabled(true); yield break; } // look for a legacy Animation component on the active state var anim = activeState.GetComponent(); if (anim == null) { // no animation to wait for; allow input immediately SetInputEnabled(true); yield break; } // determine a clip/state to observe string clipName = anim.clip != null ? anim.clip.name : null; AnimationState observedState = null; if (!string.IsNullOrEmpty(clipName)) { observedState = anim[clipName]; } else { // fallback: take the first enabled state in the Animation foreach (AnimationState st in anim) { observedState = st; break; } } if (observedState == null) { // nothing to observe; allow input SetInputEnabled(true); yield break; } // wait until the animation starts playing the observed clip float safetyTimer = 0f; while (anim.isActiveAndEnabled && activeState.gameObject.activeInHierarchy && !anim.IsPlaying(observedState.name) && safetyTimer < 2f) { safetyTimer += Time.deltaTime; yield return null; } // wait until the first loop completes (normalizedTime >= 1) while (anim.isActiveAndEnabled && activeState.gameObject.activeInHierarchy) { // if state changed (not playing anymore), allow input to avoid deadlock if (!anim.IsPlaying(observedState.name)) break; if (observedState.normalizedTime >= 1f) { break; } yield return null; } // After first loop completes, handle based on progress type if (progressType == ProgressType.Auto) { // Auto mode: immediately progress to next state _stateMachine.Next(true); SetupInputGateForCurrentState(); } else { // Manual mode: enable input and wait for player tap SetInputEnabled(true); } _waitLoopCoroutine = null; } } }