using System; using HG; using RoR2.Audio; using UnityEngine; using UnityEngine.Networking; namespace RoR2.Projectile; [RequireComponent(typeof(ProjectileController))] public class ProjectileImpactExplosion : ProjectileExplosion, IProjectileImpactBehavior { public enum TransformSpace { World, Local, Normal } private Vector3 impactNormal = Vector3.up; [Header("Impact & Lifetime Properties")] public GameObject impactEffect; [ShowFieldObsolete] [Tooltip("This sound will not play over the network. Use lifetimeExpiredSound instead.")] [HideInInspector] [Obsolete("This sound will not play over the network. Use lifetimeExpiredSound instead.", false)] public string lifetimeExpiredSoundString; public NetworkSoundEventDef lifetimeExpiredSound; public float offsetForLifetimeExpiredSound; public bool destroyOnEnemy = true; public bool destroyOnWorld; public bool impactOnWorld = true; public bool timerAfterImpact; public float lifetime; public float lifetimeAfterImpact; public float lifetimeRandomOffset; private float stopwatch; public uint minimumPhysicsStepsToKeepAliveAfterImpact; private float stopwatchAfterImpact; private bool hasImpact; private bool hasPlayedLifetimeExpiredSound; public TransformSpace transformSpace; public bool explodeOnLifeTimeExpiration; protected override void Awake() { base.Awake(); lifetime += UnityEngine.Random.Range(0f, lifetimeRandomOffset); } protected void FixedUpdate() { stopwatch += Time.fixedDeltaTime; if (!NetworkServer.active && !projectileController.isPrediction) { return; } if (explodeOnLifeTimeExpiration && alive && stopwatch >= lifetime) { explosionEffect = impactEffect ?? explosionEffect; Detonate(); } if (timerAfterImpact && hasImpact) { stopwatchAfterImpact += Time.fixedDeltaTime; } bool num = stopwatch >= lifetime; bool flag = timerAfterImpact && stopwatchAfterImpact > lifetimeAfterImpact; bool flag2 = (bool)projectileHealthComponent && !projectileHealthComponent.alive; if (num || flag || flag2) { alive = false; } if (timerAfterImpact && hasImpact && minimumPhysicsStepsToKeepAliveAfterImpact != 0) { minimumPhysicsStepsToKeepAliveAfterImpact--; alive = true; } if (alive && !hasPlayedLifetimeExpiredSound) { bool flag3 = stopwatch > lifetime - offsetForLifetimeExpiredSound; if (timerAfterImpact) { flag3 |= stopwatchAfterImpact > lifetimeAfterImpact - offsetForLifetimeExpiredSound; } if (flag3) { hasPlayedLifetimeExpiredSound = true; if (NetworkServer.active && (bool)lifetimeExpiredSound) { PointSoundManager.EmitSoundServer(lifetimeExpiredSound.index, base.transform.position); } } } if (!alive) { explosionEffect = impactEffect ?? explosionEffect; Detonate(); } } protected override Quaternion GetRandomDirectionForChild() { Quaternion randomChildRollPitch = GetRandomChildRollPitch(); return transformSpace switch { TransformSpace.Local => base.transform.rotation * randomChildRollPitch, TransformSpace.Normal => Quaternion.FromToRotation(Vector3.forward, impactNormal) * randomChildRollPitch, _ => randomChildRollPitch, }; } public void OnProjectileImpact(ProjectileImpactInfo impactInfo) { if (!alive) { return; } Collider collider = impactInfo.collider; impactNormal = impactInfo.estimatedImpactNormal; if (!collider) { return; } DamageInfo damageInfo = new DamageInfo(); if ((bool)projectileDamage) { damageInfo.damage = projectileDamage.damage; damageInfo.crit = projectileDamage.crit; damageInfo.attacker = (projectileController.owner ? projectileController.owner.gameObject : null); damageInfo.inflictor = base.gameObject; damageInfo.position = impactInfo.estimatedPointOfImpact; damageInfo.force = projectileDamage.force * base.transform.forward; damageInfo.procChainMask = projectileController.procChainMask; damageInfo.procCoefficient = projectileController.procCoefficient; } HurtBox component = collider.GetComponent(); if ((bool)component) { if (destroyOnEnemy) { HealthComponent healthComponent = component.healthComponent; if ((bool)healthComponent) { if (healthComponent.gameObject == projectileController.owner || ((bool)projectileHealthComponent && healthComponent == projectileHealthComponent)) { return; } alive = false; } } } else if (destroyOnWorld) { alive = false; } hasImpact = (bool)component || impactOnWorld; if (NetworkServer.active && hasImpact) { GlobalEventManager.instance.OnHitAll(damageInfo, collider.gameObject); } } protected override void OnValidate() { if (!Application.IsPlaying(this)) { base.OnValidate(); if (!string.IsNullOrEmpty(lifetimeExpiredSoundString)) { Debug.LogWarningFormat(base.gameObject, "{0} ProjectileImpactExplosion component supplies a value in the lifetimeExpiredSoundString field. This will not play correctly over the network. Please use lifetimeExpiredSound instead.", Util.GetGameObjectHierarchyName(base.gameObject)); } } } }