using System.Collections.Generic; using SepCore.Definition; using DG.Tweening; using SepCore.Entity; using SepCore.CustomUtility; using UnityEngine; using UnityGameFramework.Runtime; namespace SepCore.Entity { public class WeaponSlash : WeaponBase { #region Property private Sequence _attackSequence; private Vector3 _attackCenter; private Quaternion _cachedAttackRotation; private IWeaponAttackEffect _attackEffect; [SerializeField] private float _attackDuration = 0.4f; [SerializeField] private float _returnDuration = 0.4f; [SerializeField] private float _swingAngleScale = 1f; [SerializeField] private int _maxHitColliders = 32; private Collider[] _hitResults; private readonly HashSet _hitEntityIds = new(); public float SectorAngle => WeaponData.GetParamFloat(ParamType.SectorAngle); #endregion protected override void BuildStates() { RegisterState(new WeaponSlashIdleState()); RegisterState(new WeaponSlashAimState()); RegisterState(new WeaponSlashSeekState()); RegisterState(new WeaponSlashAttackState()); } public override void Attack() { StopAttackTween(false); FaceTargetImmediately(); _isAttacking = true; _attackCenter = CachedTransform.position; _cachedAttackRotation = CachedTransform.rotation; float swingAngle = Mathf.Clamp(SectorAngle * Mathf.Max(0.1f, _swingAngleScale), 15f, 170f); Quaternion startRotation = Quaternion.AngleAxis(-swingAngle * 0.5f, Vector3.up) * _cachedAttackRotation; Quaternion endRotation = Quaternion.AngleAxis(swingAngle * 0.5f, Vector3.up) * _cachedAttackRotation; CachedTransform.rotation = startRotation; _attackSequence = DOTween.Sequence(); _attackSequence.Append( CachedTransform.DORotateQuaternion(endRotation, _attackDuration).SetEase(Ease.OutSine)); _attackSequence.InsertCallback(_attackDuration * 0.5f, () => { _attackEffect.Play(this, _attackCenter, _target, CurrentAttackRange); ApplySectorDamage(); }); _attackSequence.Append(CachedTransform.DORotateQuaternion(_cachedAttackRotation, _returnDuration) .SetEase(Ease.InSine)); _attackSequence.AppendCallback(() => { _isAttacking = false; _attackSequence = null; }); } private void ApplySectorDamage() { float attackRadius = CurrentAttackRange; if (attackRadius <= 0f) return; Vector3 forward = CachedTransform.forward; forward.y = 0f; if (forward.sqrMagnitude <= Mathf.Epsilon) { forward = Vector3.forward; } forward.Normalize(); float halfAngle = SectorAngle * 0.5f; if (TryQueueSectorCollisionQuery(_attackCenter, attackRadius, in forward, halfAngle, Mathf.Max(1, _maxHitColliders))) { return; } if (_hitResults == null || _hitResults.Length == 0) return; int hitCount = Physics.OverlapSphereNonAlloc(_attackCenter, attackRadius, _hitResults, _hitMask, QueryTriggerInteraction.Collide); float attackRadiusSqr = attackRadius * attackRadius; _hitEntityIds.Clear(); for (int i = 0; i < hitCount; i++) { Collider collider = _hitResults[i]; if (collider == null) continue; TargetableObject targetable = collider.GetComponentInParent(); if (targetable == null || !targetable.Available || targetable.IsDead) continue; if (!_hitEntityIds.Add(targetable.Id)) continue; Vector3 toTarget = targetable.CachedTransform.position - _attackCenter; toTarget.y = 0f; float sqrDistance = toTarget.sqrMagnitude; if (sqrDistance <= Mathf.Epsilon || sqrDistance > attackRadiusSqr) continue; float angle = Vector3.Angle(forward, toTarget.normalized); if (angle > halfAngle) continue; AIUtility.PerformCollision(targetable, this); } } #region Lifecycle protected override bool OnWeaponShow(object userData) { _cachedAttackRotation = _cachedRotation; int capacity = Mathf.Max(1, _maxHitColliders); if (_hitResults == null || _hitResults.Length != capacity) { _hitResults = new Collider[capacity]; } _attackEffect = new SlashSectorAttackEffect(); return true; } protected override void OnWeaponHide(object userData) { StopAttackTween(true); _attackEffect = null; } protected override void OnWeaponDetach(EntityLogic parentEntity, object userData) { StopAttackTween(true); base.OnWeaponDetach(parentEntity, userData); } protected override void OnEnabledChanged(bool enabled) { if (!enabled) { StopAttackTween(true); } } #endregion private void StopAttackTween(bool resetTransform) { if (_attackSequence != null) { _attackSequence.Kill(); _attackSequence = null; } _isAttacking = false; if (resetTransform) { CachedTransform.rotation = _cachedAttackRotation; } _hitEntityIds.Clear(); } } }