324 lines
10 KiB
C#
324 lines
10 KiB
C#
using System.Collections.Generic;
|
|
using SepCore.Components;
|
|
using SepCore.Definition;
|
|
using GameFramework;
|
|
using SepCore.CustomUtility;
|
|
using UnityEngine;
|
|
using UnityGameFramework.Runtime;
|
|
|
|
namespace SepCore.Entity
|
|
{
|
|
/// <summary>
|
|
/// 武器类。
|
|
/// </summary>
|
|
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<EntityBase> 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<StatComponent>();
|
|
if (statComponent == null) return;
|
|
|
|
_ownerAttributeProvider = statComponent.Pipeline;
|
|
}
|
|
|
|
protected void ReleaseAttackStatSubscription()
|
|
{
|
|
_ownerAttributeProvider = null;
|
|
}
|
|
}
|
|
}
|