private IEnumerator ReleaseBottleScreamAsync()
    {
        _playerAnimation.PlayEmote(PlayerAnimatorController.EmoteState.OpenBottle);
        yield return(_playerAnimation.AnimatorCallbacks.WaitForEvent("OnBottleUncorked"));

        ScreamContainer bottle = _objectHolder.HeldObject.GetComponent <ScreamContainer>();

        if (bottle != null && bottle.ScreamSounds.Count > 0)
        {
            ScreamDamageable.DoScream(bottle.ScreamSounds, bottle.transform.position, transform.forward, _screamDamageable);
            bottle.ReleaseScream();
        }

        yield return(null);
    }
    public static void DoScream(IReadOnlyList <ScreamSoundDefinition> screamSounds, Vector3 fromPos, Vector3 dir, ScreamDamageable ignore = null)
    {
        Debug.DrawRay(fromPos, dir * kScreamRange, Color.green, 1);

        for (int i = 0; i < _instances.Count; ++i)
        {
            ScreamDamageable damageable = _instances[i];
            if (damageable == ignore)
            {
                continue;
            }

            Vector3 toDamageable = damageable.transform.position - fromPos;
            float   angle        = Vector3.Angle(dir.WithY(0), toDamageable.WithY(0));
            float   dist         = toDamageable.magnitude;
            if ((dist < kScreamRange && angle < kScreamAngle) || dist < kScreamMinRange)
            {
                Debug.Log($"Screaming at {damageable.name}");
                Debug.DrawRay(fromPos, toDamageable, Color.red, 10);
                damageable.NotifyScreamedAt(screamSounds);
            }
        }
    }
    void OnBehaviorStateEntered(BehaviorState newBehavior)
    {
        switch (newBehavior)
        {
        case BehaviorState.Idle:
            _throttleUrgency   = 0.0f;  // stop
            _pathRefreshPeriod = -1.0f; // no refresh
            _idleDuration      = Random.Range(IdleMinDuration, IdleMaxDuration);
            break;

        case BehaviorState.Wander:
            _throttleUrgency   = 0.5f;  // half speed
            _pathRefreshPeriod = -1.0f; // manual refresh
            // Pick a path to a wander target
            {
                Vector2 offset       = Random.insideUnitCircle * WanderRange;
                Vector3 wanderTarget = _spawnLocation + Vector3.left * offset.x + Vector3.forward * offset.y;
                RecomputePathTo(wanderTarget);
            }
            break;

        case BehaviorState.Chase:
            _throttleUrgency   = 1.0f; // full speed
            _pathRefreshPeriod = 2.0f; // refresh path every 2 seconds while persuing player
            // Force on line of sight checks even when player is out of vision cone
            _perceptionComponent.ForceLineOfSightCheck = true;
            // Start dropping player sanity while pursuit active
            GameStateManager.Instance.PlayerSanity.OnPursuitStarted();
            // Head to the player
            // If this fails we take care of it in attack update
            RecomputePathTo(GetCurrentPlayerLocation());
            break;

        case BehaviorState.Cower:
            _throttleUrgency   = 0.0f;  // Stop and sh*t yourself
            _pathRefreshPeriod = -1.0f; // manual refresh
            _aiAnimation.PlayEmote(AIAnimatorController.EmoteState.Cower);
            // Set animation dead flag early so that we don't leave emote state
            _aiAnimation.IsDead = true;
            // Hide the vision cone
            _perceptionComponent.gameObject.SetActive(false);
            break;

        case BehaviorState.Attack:
            _throttleUrgency   = 0.0f;  // Stop and attack in place
            _pathRefreshPeriod = -1.0f; // manual refresh

            _aiAnimation.PlayEmote(AIAnimatorController.EmoteState.Attack);
            _timeSinceAttack = 0.0f; // We just attacked

            var screamSounds = new List <ScreamSoundDefinition>()
            {
                _attackScream
            };
            _screamController.StartScream(screamSounds, false, 1.0f);
            ScreamDamageable.DoScream(screamSounds, _perceptionComponent.transform.position, _perceptionComponent.transform.forward, _screamDamageable);

            break;

        case BehaviorState.Flee:
            _throttleUrgency   = 1.0f;  // full speed
            _pathRefreshPeriod = -1.0f; // manual refresh
            // Head back to spawn location
            // If this fails we take care of it in flee update
            RecomputePathTo(_spawnLocation);
            break;

        case BehaviorState.Dead:
            // Play death effects to cover the transition
            if (_deathFX != null)
            {
                Instantiate(_deathFX, transform.position, Quaternion.identity);
            }

            AudioManager.Instance.PlaySound(gameObject, _deathSound);

            // Spawn a death bottles in out place
            if (_deathBottleSpawn != null)
            {
                GameObject      bottle          = Instantiate(_deathBottleSpawn, transform.position, Quaternion.identity);
                ScreamContainer screamContainer = bottle.GetComponent <ScreamContainer>();
                if (screamContainer != null)
                {
                    screamContainer.FillScream(new List <ScreamSoundDefinition>()
                    {
                        _attackScream
                    });
                }
            }
            // Clean ourselves up after a moment
            Destroy(this, 0.1f);
            break;
        }
    }