using System; using System.Collections; using System.Linq; using BlueWater.Audios; using BlueWater.Interfaces; using BlueWater.Utility; using UnityEngine; using Random = UnityEngine.Random; namespace BlueWater.Players.Combat.Skills { public class TheWaltzOfTheSword : BaseSkill { private enum Direction { None = -1, Left, Back, Right } private TheWaltzOfTheSwordData _theWaltzOfTheSwordData; private CombatPlayer _combatPlayer; private AnimationController _animationController; private IPhysicMovable _iPhysicMovable; private ParticleSystem _readyParticleInstance; private ParticleSystem _attackParticleInstance; private Coroutine _readyToSkillCoroutine; private int _hitCount; private int _attackCount; private int _currentHitCount; private float _intervalTime; private Direction _currentDirection; private bool _previousLeft; private bool _isMoved; private Vector3 _originStartPosition; private void OnDestroy() { if (_readyParticleInstance) { Destroy(_readyParticleInstance.gameObject); } if (_attackParticleInstance) { Destroy(_attackParticleInstance.gameObject); } } protected override void InitializeData() { base.InitializeData(); if (!_combatPlayer) { _combatPlayer = SkillUser.GetComponent(); _animationController = _combatPlayer.AnimationController; _iPhysicMovable = _combatPlayer.GetComponent(); } _theWaltzOfTheSwordData = (TheWaltzOfTheSwordData)SkillData; _readyParticleInstance = Instantiate(_theWaltzOfTheSwordData.ReadyParticle, SkillUser.transform); } public override bool CanSkill() { if (!EnableSkill) return false; _originStartPosition = SkillUser.transform.position; HitColliders = new Collider[_theWaltzOfTheSwordData.MaxAttackCount]; _hitCount = Physics.OverlapSphereNonAlloc(_originStartPosition, SkillData.Radius, HitColliders, _theWaltzOfTheSwordData.TargetLayer, QueryTriggerInteraction.Collide); return _hitCount > 0; } public override void ActivateSkill(params Action[] onCompleted) { Utils.StartUniqueCoroutine(this, ref _readyToSkillCoroutine, ReadyToSkillCoroutine(onCompleted)); } private IEnumerator ReadyToSkillCoroutine(params Action[] onCompleted) { EnableSkill = false; _iPhysicMovable.Rigidbody.linearVelocity = Vector3.zero; _iPhysicMovable.Rigidbody.isKinematic = true; _readyParticleInstance.Play(); SortCollidersByDistance(); _animationController.SetAnimationParameter("isReadyMainSkill", true); var animationStarted = false; yield return StartCoroutine(_animationController.WaitForAnimationToRun("ReadyToMainSkill", success => animationStarted = success)); if (!animationStarted) { EndReadyToSkill(true, onCompleted[0]); yield break; } _animationController.SetCurrentAnimationSpeed(SkillData.CastingTime); while (_animationController.IsComparingCurrentAnimation("ReadyToMainSkill") && _animationController.GetCurrentAnimationNormalizedTime() < 1f) { yield return null; } EndReadyToSkill(false, onCompleted[0]); } private void EndReadyToSkill(bool isEscaped, Action enableMove) { Utils.EndUniqueCoroutine(this, ref _readyToSkillCoroutine); _animationController.ResetAnimationSpeed(); _animationController.SetAnimationParameter("isReadyMainSkill", false); if (isEscaped) { _iPhysicMovable.Rigidbody.isKinematic = false; enableMove?.Invoke(); StartCoroutine(Utils.CoolDownCoroutine(0, () => EnableSkill = true)); } else { Utils.StartUniqueCoroutine(this, ref SkillCoroutineInstance, SkillCoroutine(enableMove)); } } private IEnumerator SkillCoroutine(params Action[] onCompleted) { _animationController.SetAnimationParameter("isActivateMainSkill", true); var animationStarted = false; yield return StartCoroutine(_animationController.WaitForAnimationToRun("MainSkill", success => animationStarted = success)); if (!animationStarted) { EndSkill(onCompleted[0]); yield break; } _animationController.SetCurrentAnimationSpeed(SkillData.Duration); AudioManager.Instance.PlaySfx("CombatPlayerMainSkill", SkillData.Duration); _intervalTime = 1f / _theWaltzOfTheSwordData.MaxAttackCount; if (_theWaltzOfTheSwordData.IsFollowingTargetCamera) { CombatCameraManager.Instance.SetFollow(null); } _currentDirection = Direction.Back; _previousLeft = false; _isMoved = false; _attackCount = 0; _currentHitCount = 0; while (_animationController.IsComparingCurrentAnimation("MainSkill") && _attackCount < _theWaltzOfTheSwordData.MaxAttackCount) { if (!_isMoved) { if (AllTargetsAreDead()) { EndSkill(onCompleted[0]); yield break; } MovePoint(HitColliders[_currentHitCount], _currentDirection); } else if (_animationController.GetCurrentAnimationNormalizedTime() >= _intervalTime * (_attackCount + 1)) { ExecuteAttackRoutine(onCompleted[0]); } yield return null; } EndSkill(onCompleted[0]); } private void MovePoint(Collider hitCollider, Direction direction) { var center = hitCollider.bounds.center; var addX = 0f; var addZ = 0f; switch(direction) { case Direction.None: break; case Direction.Left: _iPhysicMovable.SetCurrentDirection(Vector3.right); addX = -hitCollider.bounds.extents.x; _currentDirection = Direction.Back; break; case Direction.Back: addZ = hitCollider.bounds.extents.z; _currentDirection = _previousLeft ? Direction.Right : Direction.Left; _previousLeft = !_previousLeft; break; case Direction.Right: _iPhysicMovable.SetCurrentDirection(Vector3.left); addX = hitCollider.bounds.extents.x; _currentDirection = Direction.Back; break; default: throw new ArgumentOutOfRangeException(nameof(direction), direction, null); } var newPosition = new Vector3(center.x + addX, SkillUser.transform.position.y, center.z + addZ); SkillUser.transform.position = newPosition; _isMoved = true; } private void ExecuteAttackRoutine(Action enableMove) { DoAttack(HitColliders[_currentHitCount]); _attackCount++; AddCurrentNum(); for (var i = 0; i < _hitCount; i++) { if (!IsTargetAlive(HitColliders[_currentHitCount])) { AddCurrentNum(); continue; } _isMoved = false; return; } EndSkill(enableMove); } private void AddCurrentNum() { _currentHitCount = (_currentHitCount + 1) % _hitCount; } private void EndSkill(Action enableMove) { Utils.EndUniqueCoroutine(this, ref SkillCoroutineInstance); AudioManager.Instance.StopSfx("CombatPlayerMainSkill"); _animationController.ResetAnimationSpeed(); _animationController.SetAnimationParameter("isActivateMainSkill", false); enableMove?.Invoke(); _iPhysicMovable.Rigidbody.isKinematic = false; if (_theWaltzOfTheSwordData.ReturnStartPosition) { SkillUser.transform.position = _originStartPosition; } Utils.StartUniqueCoroutine(this, ref CooldownCoroutineInstance,Utils.CoolDownCoroutine(SkillData.Cooldown, EndCooldown)); } private void SortCollidersByDistance() { HitColliders = HitColliders .Where(c => c != null) .OrderBy(c => (c.transform.position - transform.position).sqrMagnitude) .ToArray(); } private void DoAttack(Collider hitCollider) { if (hitCollider == null || hitCollider.gameObject == null) return; var iDamageable = hitCollider.GetComponentInParent(); iDamageable.TakeDamage(SkillData.Damage); if (iDamageable.CurrentHealthPoint == 0f) { if (_theWaltzOfTheSwordData.IsHitStop) { VisualFeedbackManager.Instance.CameraShake(CombatCameraManager.Instance.BaseCombatCamera); VisualFeedbackManager.Instance.TriggerHitStop(_theWaltzOfTheSwordData.HitStopDuration); } } if (_theWaltzOfTheSwordData.AttackParticle) { var bounds = hitCollider.bounds; var randomPosition = new Vector3( Random.Range(bounds.min.x, bounds.max.x), Random.Range(bounds.min.y, bounds.max.y), Random.Range(bounds.min.z, bounds.max.z) ); _attackParticleInstance = Instantiate(_theWaltzOfTheSwordData.AttackParticle, randomPosition, Quaternion.identity); _attackParticleInstance.Play(); } } private bool IsTargetAlive(Collider hitCollider) { if (hitCollider == null || hitCollider.gameObject == null) return false; var damageable = hitCollider.GetComponentInParent(); return damageable is { CurrentHealthPoint: > 0 }; } private bool AllTargetsAreDead() { return HitColliders.All(c => !IsTargetAlive(c)); } } }