using UnityEngine; using Fusion; using System.Collections.Generic; namespace TPSBR { public struct ProjectileData : INetworkStruct { public Vector3 Destination; public Vector3 ImpactNormal; public int ImpactTagHash; } public sealed class HitscanWeapon : FirearmWeapon { // PRIVATE MEMBERS [Header("Hitscan Setup")] [SerializeField] private DummyProjectile _projectile; [SerializeField] private ImpactSetup _impactSetup; [SerializeField] private float _damageMultiplier = 1f; [Networked, Capacity(10)] private NetworkArray _projectileData { get; } private List _validHits = new List(16); // FirearmWeapon INTERFACE protected override bool FireProjectile(Vector3 firePosition, Vector3 targetPosition, Vector3 direction, float distanceToTarget, LayerMask hitMask, bool isFirst) { GameplayInput gameplayInput = Character.Agent.AgentInput.FixedInput; Vector3 impactNormal = Vector3.zero; Vector3 impactPosition = targetPosition; int impactTagHash = 0; int ownerObjectID = Owner != null ? Owner.gameObject.GetInstanceID() : 0; if (gameplayInput.InterpolationFromTick != default && gameplayInput.InterpolationToTick != default && gameplayInput.InterpolationAlpha != default) { ProjectileUtility.ProjectileCast(Runner, Object.InputAuthority, ownerObjectID, gameplayInput.InterpolationFromTick, gameplayInput.InterpolationToTick, gameplayInput.InterpolationAlpha, firePosition, direction, distanceToTarget, _projectile.MaxDistance, hitMask, _validHits); } else { ProjectileUtility.ProjectileCast(Runner, Object.InputAuthority, ownerObjectID, firePosition, direction, distanceToTarget, _projectile.MaxDistance, hitMask, _validHits); } float damagePenalty = 0f; float maxDamage = _projectile.GetDamage(0f); bool impactRegistered = false; for (int i = 0; i < _validHits.Count; i++) { if (damagePenalty >= maxDamage) break; var hit = _validHits[i]; float realDistance = Vector3.Distance(firePosition, hit.Point); float hitDamage = _projectile.GetDamage(realDistance) * _damageMultiplier - damagePenalty; if (hitDamage <= 0f) break; HitUtility.ProcessHit(Owner, direction, hit, hitDamage, HitType, out HitData hitData); int hitTagHash = hit.GameObject.tag.GetHashCode(); // Spawn impacts on static objects only if (impactRegistered == false && hit.GameObject.layer != ObjectLayer.Agent && hit.GameObject.layer != ObjectLayer.Target) { impactNormal = (hit.Normal + -direction) * 0.5f; impactTagHash = hitTagHash; impactPosition = hit.Point; // We want impact on first solid object impactRegistered = true; } float damageMultiplier = _projectile.Piercing != null ? _projectile.Piercing.GetDamageMultiplier(hitTagHash) : 0f; damagePenalty += maxDamage - maxDamage * damageMultiplier; } var projectileData = new ProjectileData() { Destination = impactPosition, ImpactNormal = impactNormal, ImpactTagHash = impactTagHash, }; int projectileIndex = _projectilesCount % _projectileData.Length; _projectileData.Set(projectileIndex, projectileData); return true; } protected override void FireVisualProjectile(int projectileIndex, bool playFireEffects) { var projectileData = _projectileData[projectileIndex % _projectileData.Length]; var projectileInstance = Context.ObjectCache.Get(_projectile); Runner.MoveToRunnerScene(projectileInstance); projectileInstance.Context = Context; projectileInstance.Fire(FireTransform.position, FireTransform.rotation, projectileData.Destination); if (projectileData.ImpactNormal != Vector3.zero) { var impactParticle = Context.ObjectCache.Get(_impactSetup.GetImpact(projectileData.ImpactTagHash)); Context.ObjectCache.ReturnDeferred(impactParticle, 5f); Runner.MoveToRunnerSceneExtended(impactParticle); impactParticle.transform.position = projectileData.Destination; impactParticle.transform.rotation = Quaternion.LookRotation(projectileData.ImpactNormal); } base.FireVisualProjectile(projectileIndex, playFireEffects); } } }