using System.Collections.Generic; using Fusion; using UnityEngine; namespace Projectiles { public enum EHitAction : byte { None, Damage, Heal, } public struct HitData { public EHitAction Action; public float Amount; public bool IsFatal; public Vector3 Position; public Vector3 Direction; public Vector3 Normal; public Vector3 RootPosition; public PlayerRef InstigatorRef; public IHitInstigator Instigator; public IHitTarget Target; public EHitType HitType; } public enum EHitType { None, Projectile, Explosion, Suicide, } public interface IHitTarget { bool IsActive { get; } Transform HeadPivot { get; } Transform BodyPivot { get; } Transform GroundPivot { get; } Hitbox BodyHitbox { get; } void ProcessHit(ref HitData hit); } public interface IHitInstigator { void HitPerformed(HitData hit); } /// /// A utility that encapsulates common approach of handling hits. /// public static class HitUtility { // PUBLIC METHODS public static void GetAllTargets(NetworkRunner runner, List targets, bool onlyActive = true) { targets.Clear(); var healths = ListPool.Get(targets.Count); // TODO: Best would be to get IHitTargets directly, but that is not possible for now runner.GetAllBehaviours(healths); if (onlyActive == true) { for (int i = 0; i < healths.Count; i++) { var target = healths[i] as IHitTarget; if (target.IsActive == true) targets.Add(target); } } else { targets.AddRange(healths); } ListPool.Return(healths); } public static HitData ProcessHit(PlayerRef instigatorRef, Vector3 direction, LagCompensatedHit hit, float baseDamage, EHitType hitType) { var target = GetHitTarget(hit.Hitbox, hit.Collider); if (target == null) return default; HitData hitData = default; hitData.Action = EHitAction.Damage; hitData.Amount = baseDamage; hitData.Position = hit.Point; hitData.Normal = hit.Normal; hitData.Direction = direction; hitData.Target = target; hitData.InstigatorRef = instigatorRef; hitData.HitType = hitType; return ProcessHit(ref hitData); } public static HitData ProcessHit(NetworkBehaviour instigator, Vector3 direction, LagCompensatedHit hit, float baseDamage, EHitType hitType) { var target = GetHitTarget(hit.Hitbox, hit.Collider); if (target == null) return default; HitData hitData = default; hitData.Action = EHitAction.Damage; hitData.Amount = baseDamage; hitData.Position = hit.Point; hitData.Normal = hit.Normal; hitData.Direction = direction; hitData.Target = target; hitData.InstigatorRef = instigator != null ? instigator.Object.InputAuthority : default; hitData.Instigator = instigator != null ? instigator.GetComponent() : null; hitData.HitType = hitType; return ProcessHit(ref hitData); } public static HitData ProcessHit(NetworkBehaviour instigator, Collider collider, float damage, EHitType hitType) { var target = GetHitTarget(null, collider); if (target == null) return default; HitData hitData = default; hitData.Action = EHitAction.Damage; hitData.Amount = damage; hitData.InstigatorRef = instigator.Object.InputAuthority; hitData.Instigator = instigator.GetComponent(); hitData.Position = collider.transform.position; hitData.Normal = (instigator.transform.position - collider.transform.position).normalized; hitData.Direction = -hitData.Normal; hitData.Target = target; hitData.HitType = hitType; return ProcessHit(ref hitData); } public static HitData ProcessHit(ref HitData hitData) { hitData.Target.ProcessHit(ref hitData); // For local debug targets we show hit feedback immediately // if (hitData.Instigator != null && hitData.Target is Health == false) // { // hitData.Instigator.HitPerformed(hitData); // } return hitData; } public static IHitTarget GetHitTarget(Hitbox hitbox, Collider collider) { if (hitbox != null) return hitbox.Root.GetComponent(); if (collider == null) return null; if (ObjectLayerMask.HitTargets.value.IsBitSet(collider.gameObject.layer) == false) return null; return collider.GetComponentInParent(); } } }