using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using PixelCrushers; // For Saver using UnityEngine; using Random = UnityEngine.Random; namespace Beyond { [RequireComponent(typeof(Collider))] public class BarkPlayer : Saver { [Header("Bark Configuration")] [Tooltip("The index of the BarkEntry in BarkManager's m_barks list.")] public int barkManagerEntryIndex = 0; [Tooltip("Delay in seconds after one bark's audio finishes before starting the next.")] public float delayBetweenBarks = 0.5f; [Tooltip("Play barks within the entry in a random order (shuffled once on start/load).")] public bool shuffleOrder = false; [Tooltip("Keep playing barks (looping the sequence if necessary) as long as an object on a triggering layer stays inside.")] public bool loopWhileInside = true; [Header("Trigger Settings")] [Tooltip("Layers that can activate this bark trigger.")] public LayerMask triggeringLayers; // User configures this in Inspector [Header("State (Read Only)")] [SerializeField, ReadOnly(true)] private int nextBarkSequenceIndex = 0; [SerializeField, ReadOnly(true)] private bool isTriggeringObjectInside = false; // Renamed for clarity // --- Internal Vars --- // Removed PLAYER_LAYER_NAME constant private List playbackOrder; private bool isInitialized = false; private Collider triggerCollider; private BarkManager barkManager; private Coroutine currentPlaybackCoroutine = null; private Transform currentTriggererTransform = null; // Store the transform of the object inside // --- Save Data --- (Remains the same) [System.Serializable] public class BarkPlayerData { /* ... same as before ... */ public int savedNextBarkSequenceIndex; public List savedPlaybackOrder; public bool wasShuffled; } private BarkPlayerData m_saveData = new BarkPlayerData(); // --- Methods --- public override void Awake() { base.Awake(); triggerCollider = GetComponent(); if (!triggerCollider.isTrigger) { Debug.LogWarning($"BarkPlayer on {gameObject.name}: Collider is not set to 'Is Trigger'. Forcing it.", this); triggerCollider.isTrigger = true; } // --- Set Default LayerMask if Unassigned --- if (triggeringLayers.value == 0) // LayerMask is empty/unassigned in inspector { int playerLayer = LayerMask.NameToLayer("Player"); if (playerLayer != -1) { triggeringLayers = LayerMask.GetMask("Player"); // Default to Player layer Debug.LogWarning($"BarkPlayer on {gameObject.name}: Triggering Layers not set in inspector, defaulting to 'Player' layer.", this); } else { // Layer "Player" doesn't exist, and no layers were assigned. Log an error. Debug.LogError($"BarkPlayer on {gameObject.name}: Triggering Layers is not set in the inspector, and the default 'Player' layer was not found. This trigger will likely not activate.", this); // Leave triggeringLayers as 0 (Nothing) } } // --- End LayerMask Default --- // Initialize save data defaults m_saveData.savedNextBarkSequenceIndex = 0; m_saveData.savedPlaybackOrder = null; m_saveData.wasShuffled = shuffleOrder; nextBarkSequenceIndex = 0; isTriggeringObjectInside = false; } private void Start() { barkManager = BarkManager.Instance; if (barkManager == null) { Debug.LogError($"BarkPlayer on {gameObject.name}: Could not find BarkManager instance!", this); enabled = false; return; } InitializePlaybackOrder(); isInitialized = true; } // InitializePlaybackOrder() remains the same as before private void InitializePlaybackOrder() { if (barkManager == null) return; int barkCount = barkManager.GetBarkCountInEntry(barkManagerEntryIndex); if (barkCount <= 0) { Debug.LogWarning($"BarkPlayer on {gameObject.name}: BarkEntry {barkManagerEntryIndex} has no barks. Disabling.", this); playbackOrder = new List(); enabled = false; return; } bool useLoadedOrder = m_saveData.savedPlaybackOrder != null && m_saveData.wasShuffled == shuffleOrder && m_saveData.savedPlaybackOrder.Count == barkCount; if (useLoadedOrder) { playbackOrder = new List(m_saveData.savedPlaybackOrder); } else { playbackOrder = Enumerable.Range(0, barkCount).ToList(); if (shuffleOrder) { for (int i = playbackOrder.Count - 1; i > 0; i--) { int j = Random.Range(0, i + 1); (playbackOrder[i], playbackOrder[j]) = (playbackOrder[j], playbackOrder[i]); } m_saveData.wasShuffled = true; } else { m_saveData.wasShuffled = false; } m_saveData.savedPlaybackOrder = new List(playbackOrder); } nextBarkSequenceIndex = Mathf.Clamp(m_saveData.savedNextBarkSequenceIndex, 0, playbackOrder.Count); if (nextBarkSequenceIndex >= playbackOrder.Count && !loopWhileInside) { Debug.Log($"BarkPlayer on {gameObject.name}: Sequence for entry {barkManagerEntryIndex} already completed (and not looping).", this); } } private void OnTriggerEnter(Collider other) { if (!isInitialized) return; // --- Check Layer Mask --- int otherLayer = other.gameObject.layer; // Check if the entering object's layer is in our mask if ((triggeringLayers.value & (1 << otherLayer)) == 0) { // Layer not in mask, ignore this trigger event return; } // --- End Layer Check --- // Object on a triggering layer entered Debug.Log($"BarkPlayer ({gameObject.name}): Triggering object '{other.name}' entered.", this); isTriggeringObjectInside = true; currentTriggererTransform = other.transform; // Store the transform for barking // Start playback if not already running if (currentPlaybackCoroutine == null) { currentPlaybackCoroutine = StartCoroutine(ContinuousPlaybackLoop()); } } private void OnTriggerExit(Collider other) { if (!isInitialized) return; // --- Check Layer Mask --- int otherLayer = other.gameObject.layer; if ((triggeringLayers.value & (1 << otherLayer)) == 0) { // Ignore exit events from non-triggering layers return; } // --- End Layer Check --- // Object on a triggering layer exited // Important: Only react if the exiting object is the *same one* we stored. // This handles cases where multiple triggering objects might enter/exit. if (other.transform == currentTriggererTransform) { Debug.Log($"BarkPlayer ({gameObject.name}): Triggering object '{other.name}' exited.", this); isTriggeringObjectInside = false; currentTriggererTransform = null; // Clear the stored transform // Stop the playback coroutine if (currentPlaybackCoroutine != null) { StopCoroutine(currentPlaybackCoroutine); currentPlaybackCoroutine = null; Debug.Log($"BarkPlayer ({gameObject.name}): Stopping playback loop due to exit.", this); // Optional: Stop BarkManager audio immediately // if (barkManager != null && barkManager.IsPlaying) barkManager.m_audioSource.Stop(); } } // Else: Some other object on a triggering layer exited, but the primary one is still inside. Do nothing. } // ContinuousPlaybackLoop now uses the stored currentTriggererTransform private IEnumerator ContinuousPlaybackLoop() { Debug.Log($"BarkPlayer ({gameObject.name}): Starting playback loop.", this); // Ensure we have a target transform before proceeding while (currentTriggererTransform == null && isTriggeringObjectInside) { Debug.LogWarning($"BarkPlayer ({gameObject.name}): Waiting for valid triggerer transform.", this); yield return null; // Wait a frame if trigger happened but transform wasn't stored yet (unlikely but safe) } // Main loop runs while a triggering object is inside AND we have its transform while (isTriggeringObjectInside && currentTriggererTransform != null) { if (playbackOrder == null || playbackOrder.Count == 0) { Debug.LogWarning($"BarkPlayer ({gameObject.name}): No barks to play in entry {barkManagerEntryIndex}.", this); yield break; } bool sequenceFinished = nextBarkSequenceIndex >= playbackOrder.Count; if (sequenceFinished) { if (loopWhileInside) { Debug.Log($"BarkPlayer ({gameObject.name}): Sequence finished, looping.", this); nextBarkSequenceIndex = 0; if (shuffleOrder) { InitializePlaybackOrder(); nextBarkSequenceIndex = 0; if (playbackOrder.Count == 0) yield break; } } else { Debug.Log($"BarkPlayer ({gameObject.name}): Sequence finished, not looping. Waiting for exit.", this); // Wait until trigger object leaves while(isTriggeringObjectInside && currentTriggererTransform != null) { yield return null; } yield break; // Exit coroutine } } // Wait for BarkManager's AudioSource if necessary if (barkManager.IsPlaying) { yield return null; continue; } // Play the bark using the stored transform int barkIndexToPlay = playbackOrder[nextBarkSequenceIndex]; Debug.Log($"BarkPlayer ({gameObject.name}): Playing bark {nextBarkSequenceIndex} (actual index: {barkIndexToPlay}) targeting '{currentTriggererTransform.name}'.", this); // Pass the stored transform AudioClip playedClip = barkManager.PlayBark(barkManagerEntryIndex, currentTriggererTransform, barkIndexToPlay); nextBarkSequenceIndex++; float waitTime = delayBetweenBarks; if (playedClip != null) { waitTime += Mathf.Max(0f, playedClip.length); } if (waitTime > 0) { yield return new WaitForSeconds(waitTime); } else { yield return null; } } Debug.Log($"BarkPlayer ({gameObject.name}): Playback loop finished (Triggering object left or transform lost).", this); // Reset coroutine reference *if* it was this instance that finished it // (OnTriggerExit might have already cleared it) if (currentPlaybackCoroutine != null && !isTriggeringObjectInside) { currentPlaybackCoroutine = null; } } // --- Save System Integration --- (Remains the same) public override string RecordData() { /* ... same as before ... */ m_saveData.savedNextBarkSequenceIndex = this.nextBarkSequenceIndex; m_saveData.savedPlaybackOrder = new List(this.playbackOrder ?? new List()); m_saveData.wasShuffled = this.shuffleOrder; return SaveSystem.Serialize(m_saveData); } public override void ApplyData(string s) { /* ... same as before ... */ if (string.IsNullOrEmpty(s)) return; var loadedData = SaveSystem.Deserialize(s); if (loadedData != null) { m_saveData = loadedData; } else Debug.LogError($"BarkPlayer ({gameObject.name}): Failed to deserialize save data.", this); } // --- Gizmos --- (Remain the same, uses isTriggeringObjectInside now) private void OnDrawGizmos() { Collider col = GetComponent(); if (col != null) { Gizmos.color = isTriggeringObjectInside ? new Color(1f, 0.5f, 0.5f, 0.4f) : new Color(0.5f, 1f, 0.5f, 0.3f); if (col is BoxCollider box) Gizmos.DrawCube(transform.TransformPoint(box.center), Vector3.Scale(box.size, transform.lossyScale)); else if (col is SphereCollider sphere) Gizmos.DrawSphere(transform.TransformPoint(sphere.center), sphere.radius * MaxComponent(transform.lossyScale)); } } private float MaxComponent(Vector3 v) => Mathf.Max(Mathf.Max(v.x, v.y), v.z); } }