using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using RoR2.Audio; using UnityEngine; using UnityEngine.Networking; namespace RoR2.Projectile; [RequireComponent(typeof(TeamFilter))] public class ProjectileController : NetworkBehaviour { [HideInInspector] [Tooltip("This is assigned to the prefab automatically by ProjectileCatalog at runtime. Do not set this value manually.")] public int catalogIndex = -1; [Tooltip("The prefab to instantiate as the visual representation of this projectile. The prefab must have a ProjectileGhostController attached.")] public GameObject ghostPrefab; [Tooltip("The transform for the ghost to follow. If null, the transform of this object will be used instead.")] public Transform ghostTransformAnchor; [Tooltip("The sound to play on Start(). Use this field to ensure the sound only plays once when prediction creates two instances.")] public string startSound; [Tooltip("Prevents this projectile from being deleted by gameplay events, like Captain's defense matrix.")] public bool cannotBeDeleted; [Tooltip("Check this if you want collision events to only run on the local authority (which may be a client instead of the host). 99% of projectiles should have this set to false.")] public bool authorityHandlesCollisionEvents; [SerializeField] [Tooltip("The sound loop to play while this object exists. Use this field to ensure the sound only plays once when prediction creates two instances.")] private LoopSoundDef flightSoundLoop; protected Rigidbody rigidbody; public bool canImpactOnTrigger; public bool allowPrediction = true; [NonSerialized] [SyncVar] public ushort predictionId; [SyncVar] [HideInInspector] public GameObject owner; [HideInInspector] public bool localCollisionHappened; public float procCoefficient = 1f; private Collider[] myColliders; protected EffectManagerHelper _efhGhost; public bool CheckChildrenForCollidersAndIncludeDisabled; private NetworkInstanceId ___ownerNetId; public TeamFilter teamFilter { get; private set; } public ProjectileGhostController ghost { get; set; } public bool isPrediction { get; set; } public bool shouldPlaySounds { get; set; } public ProcChainMask procChainMask { get; set; } public NetworkConnection clientAuthorityOwner { get; private set; } public ushort NetworkpredictionId { get { return predictionId; } [param: In] set { SetSyncVar(value, ref predictionId, 1u); } } public GameObject Networkowner { get { return owner; } [param: In] set { SetSyncVarGameObject(value, ref owner, 2u, ref ___ownerNetId); } } public event Action onInitialized; private void Awake() { rigidbody = GetComponent(); teamFilter = GetComponent(); if (CheckChildrenForCollidersAndIncludeDisabled) { myColliders = GetComponentsInChildren(includeInactive: true); } else { myColliders = GetComponents(); } for (int i = 0; i < myColliders.Length; i++) { myColliders[i].enabled = false; } } private void Start() { for (int i = 0; i < myColliders.Length; i++) { myColliders[i].enabled = true; } IgnoreCollisionsWithOwner(shouldIgnore: true); if (!isPrediction && !NetworkServer.active) { ProjectileManager.instance.OnClientProjectileReceived(this); } GameObject gameObject = ProjectileGhostReplacementManager.FindProjectileGhostPrefab(this); shouldPlaySounds = false; if (isPrediction || !allowPrediction || !base.hasAuthority) { shouldPlaySounds = true; if ((bool)gameObject) { Transform transform = base.transform; if ((bool)ghostTransformAnchor) { transform = ghostTransformAnchor; } if (!EffectManager.ShouldUsePooledEffect(gameObject)) { ghost = UnityEngine.Object.Instantiate(gameObject, transform.position, transform.rotation).GetComponent(); } else { _efhGhost = EffectManager.GetAndActivatePooledEffect(gameObject, transform.position, transform.rotation); ghost = _efhGhost.gameObject.GetComponent(); } if (isPrediction) { ghost.predictionTransform = transform; } else { ghost.authorityTransform = transform; } ghost.enabled = true; } } clientAuthorityOwner = GetComponent().clientAuthorityOwner; if (shouldPlaySounds) { PointSoundManager.EmitSoundLocal((AkEventIdArg)startSound, base.transform.position); if ((bool)flightSoundLoop) { Util.PlaySound(flightSoundLoop.startSoundName, base.gameObject); } } } private void OnDestroy() { if (NetworkServer.active && ProjectileManager.instance != null) { ProjectileManager.instance.OnServerProjectileDestroyed(this); } if (shouldPlaySounds && (bool)flightSoundLoop) { Util.PlaySound(flightSoundLoop.stopSoundName, base.gameObject); } DisconnectFromGhost(); } public void DisconnectFromGhost() { if (!ghost || (isPrediction && (bool)ghost.authorityTransform)) { return; } RunGhostParentProjectileDestroyedMethod(); if (!EffectManager.UsePools) { UnityEngine.Object.Destroy(ghost.gameObject); } else { if (!isPrediction && _efhGhost == null) { _efhGhost = ghost.emh; } if (_efhGhost != null && _efhGhost.OwningPool != null) { if (!_efhGhost.HasDestroyOnTimer && !_efhGhost.HasAnimateShaderAlphas) { _efhGhost.OwningPool.ReturnObject(_efhGhost); } } else { if (_efhGhost != null) { Debug.LogFormat("efhGhost has no owning pool {0} {1}", _efhGhost.gameObject.name, _efhGhost.gameObject.GetInstanceID()); } UnityEngine.Object.Destroy(ghost.gameObject); } } _efhGhost = null; ghost = null; } public void RunGhostParentProjectileDestroyedMethod() { if ((bool)ghost) { ghost.OnParentProjectileDestroyed(); } } private void OnEnable() { InstanceTracker.Add(this); IgnoreCollisionsWithOwner(shouldIgnore: true); } private void OnDisable() { InstanceTracker.Remove(this); } public void IgnoreCollisionsWithOwner(bool shouldIgnore) { if (!owner) { return; } ModelLocator component = owner.GetComponent(); if (!component) { return; } Transform modelTransform = component.modelTransform; if (!modelTransform) { return; } HurtBoxGroup component2 = modelTransform.GetComponent(); if (!component2) { return; } HurtBox[] hurtBoxes = component2.hurtBoxes; for (int i = 0; i < hurtBoxes.Length; i++) { List gameObjectComponents = GetComponentsCache.GetGameObjectComponents(hurtBoxes[i].gameObject); int j = 0; for (int count = gameObjectComponents.Count; j < count; j++) { Collider collider = gameObjectComponents[j]; for (int k = 0; k < myColliders.Length; k++) { Collider collider2 = myColliders[k]; Physics.IgnoreCollision(collider, collider2, shouldIgnore); } } GetComponentsCache.ReturnBuffer(gameObjectComponents); } } private static Vector3 EstimateContactPoint(ContactPoint[] contacts, Collider collider) { if (contacts.Length == 0) { return collider.transform.position; } return contacts[0].point; } private static Vector3 EstimateContactNormal(ContactPoint[] contacts) { if (contacts.Length == 0) { return Vector3.zero; } return contacts[0].normal; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool CanProcessCollisionEvents() { if ((authorityHandlesCollisionEvents || !NetworkServer.active) && (!authorityHandlesCollisionEvents || !Util.HasEffectiveAuthority(base.gameObject))) { return isPrediction; } return true; } public void OnCollisionEnter(Collision collision) { localCollisionHappened = true; if (CanProcessCollisionEvents()) { ContactPoint[] contacts = collision.contacts; ProjectileImpactInfo projectileImpactInfo = default(ProjectileImpactInfo); projectileImpactInfo.collider = collision.collider; projectileImpactInfo.estimatedPointOfImpact = EstimateContactPoint(contacts, collision.collider); projectileImpactInfo.estimatedImpactNormal = EstimateContactNormal(contacts); ProjectileImpactInfo impactInfo = projectileImpactInfo; IProjectileImpactBehavior[] components = GetComponents(); for (int i = 0; i < components.Length; i++) { components[i].OnProjectileImpact(impactInfo); } } } public void DispatchOnInitialized() { this.onInitialized?.Invoke(this); } private void OnValidate() { if (!Application.IsPlaying(this)) { bool flag = GetComponent().localPlayerAuthority; if (allowPrediction && !flag) { Debug.LogWarningFormat(base.gameObject, "ProjectileController: {0} allows predictions, so it should have localPlayerAuthority=true", base.gameObject); } } } private void UNetVersion() { } public override bool OnSerialize(NetworkWriter writer, bool forceAll) { if (forceAll) { writer.WritePackedUInt32(predictionId); writer.Write(owner); return true; } bool flag = false; if ((base.syncVarDirtyBits & (true ? 1u : 0u)) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.WritePackedUInt32(predictionId); } if ((base.syncVarDirtyBits & 2u) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.Write(owner); } if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); } return flag; } public override void OnDeserialize(NetworkReader reader, bool initialState) { if (initialState) { predictionId = (ushort)reader.ReadPackedUInt32(); ___ownerNetId = reader.ReadNetworkId(); return; } int num = (int)reader.ReadPackedUInt32(); if (((uint)num & (true ? 1u : 0u)) != 0) { predictionId = (ushort)reader.ReadPackedUInt32(); } if (((uint)num & 2u) != 0) { owner = reader.ReadGameObject(); } } public override void PreStartClient() { if (!___ownerNetId.IsEmpty()) { Networkowner = ClientScene.FindLocalObject(___ownerNetId); } } }