using System.Collections.Generic; using SepCore.Components; using SepCore.Definition; using GameFramework; using SepCore.CustomUtility; using UnityEngine; using UnityGameFramework.Runtime; namespace SepCore.Entity { /// /// 武器类。 /// public abstract class WeaponBase : EntityBase { protected bool _isAttacking = false; [SerializeField] protected bool _isEnabled = false; public WeaponData WeaponData { get; protected set; } public bool IsAttacking => _isAttacking; protected ITargetSelector TargetSelector { get; private set; } protected WeaponStateMachine StateMachine { get; private set; } protected EntityBase _target; public EntityBase Target => _target; public float CurrentCooldown { get { if (WeaponData == null) return 0f; float attackSpeed = _ownerAttributeProvider?.GetValue(StatType.AttackSpeed) ?? 1f; float speedFactor = Mathf.Max(0.2f, attackSpeed); return Mathf.Max(0.02f, WeaponData.Cooldown / speedFactor); } } public float CurrentAttackRange { get { if (WeaponData == null) return 0f; float rangeBonus = _ownerAttributeProvider?.GetValue(StatType.Range) ?? 0f; return Mathf.Max(0.5f, WeaponData.AttackRange + rangeBonus); } } public float SqrRange => CurrentAttackRange * CurrentAttackRange; private IAttributeProvider _ownerAttributeProvider; private static float _rotateSpeed => GameEntry.Luban.Global.WeaponRotateSpeed; protected LayerMask _hitMask; protected Quaternion _cachedRotation; #region Lifecycle protected override void OnInit(object userData) { base.OnInit(userData); TargetSelector = new NearestTargetSelector(); } protected sealed override void OnShow(object userData) { base.OnShow(userData); WeaponData = (WeaponData)userData; if (WeaponData == null) { Log.Error("WeaponData is Invalid!"); return; } StartAttackCooldown(CurrentCooldown); _cachedRotation = CachedTransform.rotation; if (WeaponData.OwnerCamp == CampType.Player) { gameObject.layer = Constant.Layer.PlayerWeaponLayerId; _hitMask = Constant.Layer.EnemyLayerMask; } else if (WeaponData.OwnerCamp == CampType.Enemy) { gameObject.layer = Constant.Layer.EnemyWeaponLayerId; _hitMask = Constant.Layer.PlayerLayerMask; } if (!OnWeaponShow(userData)) return; EnsureStatesBuilt(); TransitionTo(InitialState); } protected sealed override void OnHide(bool isShutdown, object userData) { CancelAttackCooldown(); ReleaseAttackStatSubscription(); OnWeaponHide(userData); base.OnHide(isShutdown, userData); } protected sealed override void OnAttachTo(EntityLogic parentEntity, Transform parentTransform, object userData) { base.OnAttachTo(parentEntity, parentTransform, userData); Name = Utility.Text.Format("Weapon of {0}", parentEntity.Name); CachedTransform.localPosition = Vector3.zero; OnWeaponAttach(parentEntity, parentTransform, userData); } protected sealed override void OnDetachFrom(EntityLogic parentEntity, object userData) { OnWeaponDetach(parentEntity, userData); base.OnDetachFrom(parentEntity, userData); } protected virtual bool OnWeaponShow(object userData) => true; protected virtual void OnWeaponHide(object userData) { } protected virtual void OnWeaponAttach(EntityLogic parentEntity, Transform parentTransform, object userData) { BindAttackStatFromOwner(parentEntity); } protected virtual void OnWeaponDetach(EntityLogic parentEntity, object userData) { ReleaseAttackStatSubscription(); } protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) { base.OnUpdate(elapseSeconds, realElapseSeconds); if (!_isEnabled || StateMachine == null) return; StateMachine.Update(elapseSeconds, realElapseSeconds); } #endregion public virtual ImpactData GetImpactData() { float baseCrit = GameEntry.Luban != null && GameEntry.Luban.IsReady ? GameEntry.Luban.Global.WeaponBaseCrit : 0.05f; float critMult = GameEntry.Luban != null && GameEntry.Luban.IsReady ? GameEntry.Luban.Global.CritMultiplier : 1.5f; return new ImpactData( WeaponData.OwnerCamp, WeaponData.Attack, _ownerAttributeProvider, WeaponData.DamageType, 1.0f, baseCrit, critMult); } public abstract void Attack(); public void ResetAttackCooldown() { ResetAttackCooldown(CurrentCooldown); } public virtual void Check() { _target = SelectTarget(SqrRange); } public virtual void RotateToTarget(float elapseSeconds) { if (_target == null || !_target.Available) return; Vector3 directionToTarget = _target.CachedTransform.position - CachedTransform.position; directionToTarget.y = 0f; if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return; Quaternion startRotation = CachedTransform.rotation; Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up); CachedTransform.rotation = Quaternion.Slerp(startRotation, targetRotation, _rotateSpeed * elapseSeconds); } public virtual void RotateToOrigin(float elapseSeconds) { Quaternion startRotation = CachedTransform.rotation; CachedTransform.rotation = Quaternion.Slerp(startRotation, _cachedRotation, _rotateSpeed * elapseSeconds); } protected void FaceTargetImmediately() { if (_target == null || !_target.Available) return; Vector3 directionToTarget = _target.CachedTransform.position - CachedTransform.position; directionToTarget.y = 0f; if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return; CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up); } protected abstract void BuildStates(); protected WeaponStateType InitialState => WeaponStateType.Idle; protected void RegisterState(WeaponStateBase state) { if (state == null) { Log.Error("Weapon state is invalid."); return; } StateMachine.Register(state); } protected void EnsureStatesBuilt() { if (StateMachine != null) return; StateMachine = new WeaponStateMachine(this); BuildStates(); } public void TransitionTo(WeaponStateType newState) { if (!StateMachine.TryTransition(newState)) { Log.Error("Weapon state not found: {0}", newState); } } public void SetEnabled(bool value) { if (value == _isEnabled) return; _isEnabled = value; OnEnabledChanged(value); if (StateMachine == null || !StateMachine.IsBuilt) return; if (value) { TransitionTo(InitialState); return; } if (StateMachine.Contains(WeaponStateType.None)) { TransitionTo(WeaponStateType.None); } } protected virtual void OnEnabledChanged(bool enabled) { } protected IEnumerable GetCandidates() { return GameEntry.EnemyManager.Enemies; } protected EntityBase SelectTarget(float maxSqrRange) { return TargetSelector.SelectTarget(this, GetCandidates(), maxSqrRange); } public bool IsInRange(EntityBase target, float sqrRange) { if (target == null) return false; return AIUtility.GetSqrMagnitudeXZ(this, target) < sqrRange; } protected bool TryQueueAreaCollisionQuery(in Vector3 center, float radius, int maxTargets = 16) { return GameEntry.SimulationWorld.TryRequestAreaCollision(Id, WeaponData.OwnerId, in center, radius, maxTargets); } protected bool TryQueueSectorCollisionQuery(in Vector3 center, float radius, in Vector3 direction, float halfAngleDeg, int maxTargets = 16) { return GameEntry.SimulationWorld.TryRequestSectorCollision(Id, WeaponData.OwnerId, in center, radius, in direction, halfAngleDeg, maxTargets); } protected bool TryQueueRectangleCollisionQuery(in Vector3 center, float halfWidth, float halfLength, in Vector3 direction, int maxTargets = 16) { return GameEntry.SimulationWorld.TryRequestRectangleCollision(Id, WeaponData.OwnerId, in center, halfWidth, halfLength, in direction, maxTargets); } protected void BindAttackStatFromOwner(EntityLogic parentEntity) { ReleaseAttackStatSubscription(); if (parentEntity is not Player player) return; var statComponent = player.GetComponent(); if (statComponent == null) return; _ownerAttributeProvider = statComponent.Pipeline; } protected void ReleaseAttackStatSubscription() { _ownerAttributeProvider = null; } } }