using Fusion; using UnityEngine; namespace Projectiles { /// /// Deals damage to all IHitTargets within specified radius right after spawning. /// public class Explosion : ContextBehaviour { // PRIVATE MEMBERS [SerializeField] private LayerMask _targetMask; [SerializeField] private LayerMask _blockingMask; [SerializeField] private EHitType _hitType = EHitType.Explosion; [SerializeField, Tooltip("It is usually better to check from point slightly above explosion to filter out terrain discrepancies")] private Vector3 _explosionCheckOffset = new(0f, 0.5f, 0f); [SerializeField] private float _innerRadius = 1f; [SerializeField] private float _outerRadius = 6f; [SerializeField] private float _innerDamage = 100f; [SerializeField] private float _outerDamage = 10f; [SerializeField] private bool _canDamageOwner = true; [SerializeField] private float _despawnDelay = 3f; [SerializeField] private Transform _effectRoot; private TickTimer _despawnTimer; // NetworkBehaviour INTERFACE public override void Spawned() { ShowEffect(); if (HasStateAuthority == true) { Explode(); } _despawnTimer = TickTimer.CreateFromSeconds(Runner, _despawnDelay); } public override void FixedUpdateNetwork() { if (HasStateAuthority == false) return; if (_despawnTimer.Expired(Runner) == false) return; Runner.Despawn(Object); } // PRIVATE METHODS private void Explode() { var hits = ListPool.Get(16); var hitRoots = ListPool.Get(16); var position = transform.position + _explosionCheckOffset; var hitOptions = HitOptions.IncludePhysX; if (_canDamageOwner == false) { hitOptions |= HitOptions.IgnoreInputAuthority; } int count = Runner.LagCompensation.OverlapSphere(position, _outerRadius, Object.InputAuthority, hits, _targetMask, hitOptions); bool damageFalloff = _innerRadius < _outerRadius && _innerDamage != _outerDamage; for (int i = 0; i < count; i++) { var hit = hits[i]; var hitTarget = HitUtility.GetHitTarget(hit.Hitbox, hit.Collider); if (hitTarget == null) continue; int hitRootID = hit.Hitbox != null ? hit.Hitbox.Root.GetInstanceID() : 0; if (hitRoots.Contains(hitRootID) == true) continue; // Same object was hit multiple times var direction = hit.GameObject.transform.position - position; float distance = direction.magnitude; direction /= distance; // Normalize // Check if direction to the hitbox is not obstructed if (Runner.GetPhysicsScene().Raycast(position, direction, distance, _blockingMask) == true) continue; if (hitRootID != 0) { hitRoots.Add(hitRootID); } float damage = _innerDamage; if (damageFalloff == true && distance > _innerRadius) { damage = MathUtility.Map(_innerRadius, _outerRadius, _innerDamage, _outerDamage, distance); } hit.Point = hit.GameObject.transform.position; hit.Normal = -direction; HitUtility.ProcessHit(Object.InputAuthority, direction, hit, damage, _hitType); } ListPool.Return(hitRoots); ListPool.Return(hits); } private void ShowEffect() { if (Runner.Mode == SimulationModes.Server) return; if (_effectRoot != null) { _effectRoot.SetActive(true); _effectRoot.localScale = Vector3.one * _outerRadius * 2f; } } } }