413 lines
10 KiB
C#
413 lines
10 KiB
C#
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<ProjectileController> onInitialized;
|
|
|
|
private void Awake()
|
|
{
|
|
rigidbody = GetComponent<Rigidbody>();
|
|
teamFilter = GetComponent<TeamFilter>();
|
|
if (CheckChildrenForCollidersAndIncludeDisabled)
|
|
{
|
|
myColliders = GetComponentsInChildren<Collider>(includeInactive: true);
|
|
}
|
|
else
|
|
{
|
|
myColliders = GetComponents<Collider>();
|
|
}
|
|
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<ProjectileGhostController>();
|
|
}
|
|
else
|
|
{
|
|
_efhGhost = EffectManager.GetAndActivatePooledEffect(gameObject, transform.position, transform.rotation);
|
|
ghost = _efhGhost.gameObject.GetComponent<ProjectileGhostController>();
|
|
}
|
|
if (isPrediction)
|
|
{
|
|
ghost.predictionTransform = transform;
|
|
}
|
|
else
|
|
{
|
|
ghost.authorityTransform = transform;
|
|
}
|
|
ghost.enabled = true;
|
|
}
|
|
}
|
|
clientAuthorityOwner = GetComponent<NetworkIdentity>().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<ModelLocator>();
|
|
if (!component)
|
|
{
|
|
return;
|
|
}
|
|
Transform modelTransform = component.modelTransform;
|
|
if (!modelTransform)
|
|
{
|
|
return;
|
|
}
|
|
HurtBoxGroup component2 = modelTransform.GetComponent<HurtBoxGroup>();
|
|
if (!component2)
|
|
{
|
|
return;
|
|
}
|
|
HurtBox[] hurtBoxes = component2.hurtBoxes;
|
|
for (int i = 0; i < hurtBoxes.Length; i++)
|
|
{
|
|
List<Collider> gameObjectComponents = GetComponentsCache<Collider>.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<Collider>.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<IProjectileImpactBehavior>();
|
|
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<NetworkIdentity>().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);
|
|
}
|
|
}
|
|
}
|