164 lines
4.1 KiB
C#
164 lines
4.1 KiB
C#
|
using System.Collections.Generic;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Events;
|
||
|
using UnityEngine.Networking;
|
||
|
using UnityEngine.Serialization;
|
||
|
|
||
|
namespace RoR2.Projectile;
|
||
|
|
||
|
[RequireComponent(typeof(ProjectileTargetComponent))]
|
||
|
[RequireComponent(typeof(TeamFilter))]
|
||
|
public class ProjectileSphereTargetFinder : MonoBehaviour
|
||
|
{
|
||
|
[Tooltip("How far ahead the projectile should look to find a target.")]
|
||
|
public float lookRange;
|
||
|
|
||
|
[Tooltip("How long before searching for a target.")]
|
||
|
public float targetSearchInterval = 0.5f;
|
||
|
|
||
|
[Tooltip("Will not search for new targets once it has one.")]
|
||
|
public bool onlySearchIfNoTarget;
|
||
|
|
||
|
[Tooltip("Allows the target to be lost if it's outside the acceptable range.")]
|
||
|
public bool allowTargetLoss;
|
||
|
|
||
|
[Tooltip("If set, targets can only be found when there is a free line of sight.")]
|
||
|
public bool testLoS;
|
||
|
|
||
|
[Tooltip("Whether or not airborne characters should be ignored.")]
|
||
|
public bool ignoreAir;
|
||
|
|
||
|
[Tooltip("The difference in altitude at which a result will be ignored.")]
|
||
|
[FormerlySerializedAs("altitudeTolerance")]
|
||
|
public float flierAltitudeTolerance = float.PositiveInfinity;
|
||
|
|
||
|
public UnityEvent onNewTargetFound;
|
||
|
|
||
|
public UnityEvent onTargetLost;
|
||
|
|
||
|
private new Transform transform;
|
||
|
|
||
|
private TeamFilter teamFilter;
|
||
|
|
||
|
private ProjectileTargetComponent targetComponent;
|
||
|
|
||
|
private float searchTimer;
|
||
|
|
||
|
private SphereSearch sphereSearch;
|
||
|
|
||
|
private bool hasTarget;
|
||
|
|
||
|
private bool hadTargetLastUpdate;
|
||
|
|
||
|
private HurtBox lastFoundHurtBox;
|
||
|
|
||
|
private Transform lastFoundTransform;
|
||
|
|
||
|
private static readonly List<HurtBox> foundHurtBoxes = new List<HurtBox>();
|
||
|
|
||
|
private void Start()
|
||
|
{
|
||
|
if (!NetworkServer.active)
|
||
|
{
|
||
|
base.enabled = false;
|
||
|
return;
|
||
|
}
|
||
|
transform = base.transform;
|
||
|
teamFilter = GetComponent<TeamFilter>();
|
||
|
targetComponent = GetComponent<ProjectileTargetComponent>();
|
||
|
sphereSearch = new SphereSearch();
|
||
|
searchTimer = 0f;
|
||
|
}
|
||
|
|
||
|
private void FixedUpdate()
|
||
|
{
|
||
|
searchTimer -= Time.fixedDeltaTime;
|
||
|
if (!(searchTimer <= 0f))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
searchTimer += targetSearchInterval;
|
||
|
if (allowTargetLoss && (object)targetComponent.target != null && (object)lastFoundTransform == targetComponent.target && !PassesFilters(lastFoundHurtBox))
|
||
|
{
|
||
|
SetTarget(null);
|
||
|
}
|
||
|
if (!onlySearchIfNoTarget || targetComponent.target == null)
|
||
|
{
|
||
|
SearchForTarget();
|
||
|
}
|
||
|
hasTarget = targetComponent.target != null;
|
||
|
if (hadTargetLastUpdate != hasTarget)
|
||
|
{
|
||
|
if (hasTarget)
|
||
|
{
|
||
|
onNewTargetFound?.Invoke();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
onTargetLost?.Invoke();
|
||
|
}
|
||
|
}
|
||
|
hadTargetLastUpdate = hasTarget;
|
||
|
}
|
||
|
|
||
|
private bool PassesFilters(HurtBox result)
|
||
|
{
|
||
|
CharacterBody body = result.healthComponent.body;
|
||
|
if (!body || (ignoreAir && body.isFlying))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (body.isFlying && !float.IsInfinity(flierAltitudeTolerance) && flierAltitudeTolerance < Mathf.Abs(result.transform.position.y - transform.position.y))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private void SearchForTarget()
|
||
|
{
|
||
|
sphereSearch.origin = transform.position;
|
||
|
sphereSearch.radius = lookRange;
|
||
|
sphereSearch.mask = LayerIndex.entityPrecise.mask;
|
||
|
sphereSearch.queryTriggerInteraction = QueryTriggerInteraction.UseGlobal;
|
||
|
sphereSearch.RefreshCandidates();
|
||
|
sphereSearch.FilterCandidatesByHurtBoxTeam(TeamMask.GetEnemyTeams(teamFilter.teamIndex));
|
||
|
sphereSearch.OrderCandidatesByDistance();
|
||
|
sphereSearch.FilterCandidatesByDistinctHurtBoxEntities();
|
||
|
sphereSearch.GetHurtBoxes(foundHurtBoxes);
|
||
|
HurtBox target = null;
|
||
|
if (foundHurtBoxes.Count > 0)
|
||
|
{
|
||
|
int i = 0;
|
||
|
for (int count = foundHurtBoxes.Count; i < count; i++)
|
||
|
{
|
||
|
if (PassesFilters(foundHurtBoxes[i]))
|
||
|
{
|
||
|
target = foundHurtBoxes[i];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
foundHurtBoxes.Clear();
|
||
|
}
|
||
|
SetTarget(target);
|
||
|
}
|
||
|
|
||
|
private void SetTarget(HurtBox hurtBox)
|
||
|
{
|
||
|
lastFoundHurtBox = hurtBox;
|
||
|
lastFoundTransform = hurtBox?.transform;
|
||
|
targetComponent.target = lastFoundTransform;
|
||
|
}
|
||
|
|
||
|
private void OnDrawGizmosSelected()
|
||
|
{
|
||
|
Gizmos.color = Color.yellow;
|
||
|
Vector3 position = base.transform.position;
|
||
|
Gizmos.DrawWireSphere(position, lookRange);
|
||
|
if (!float.IsInfinity(flierAltitudeTolerance))
|
||
|
{
|
||
|
Gizmos.DrawWireCube(position, new Vector3(lookRange * 2f, flierAltitudeTolerance * 2f, lookRange * 2f));
|
||
|
}
|
||
|
}
|
||
|
}
|