using System.Collections.Generic; using RoR2.Audio; using RoR2.Networking; using UnityEngine; using UnityEngine.Networking; namespace RoR2; public class OverlapAttack { private struct OverlapInfo { public HurtBox hurtBox; public Vector3 hitPosition; public Vector3 pushDirection; } public struct AttackInfo { public GameObject attacker; public GameObject inflictor; public float damage; public bool isCrit; public float procCoefficient; public DamageColorIndex damageColorIndex; public DamageTypeCombo damageType; public Vector3 forceVector; } private class OverlapAttackMessage : MessageBase { public GameObject attacker; public GameObject inflictor; public float damage; public bool isCrit; public ProcChainMask procChainMask; public float procCoefficient; public DamageColorIndex damageColorIndex; public DamageTypeCombo damageType; public Vector3 forceVector; public float pushAwayForce; public readonly List overlapInfoList = new List(); public override void Serialize(NetworkWriter writer) { base.Serialize(writer); writer.Write(attacker); writer.Write(inflictor); writer.Write(damage); writer.Write(isCrit); writer.Write(procChainMask); writer.Write(procCoefficient); writer.Write(damageColorIndex); writer.WriteDamageType(damageType); writer.Write(forceVector); writer.Write(pushAwayForce); writer.WritePackedUInt32((uint)overlapInfoList.Count); foreach (OverlapInfo overlapInfo in overlapInfoList) { writer.Write(HurtBoxReference.FromHurtBox(overlapInfo.hurtBox)); writer.Write(overlapInfo.hitPosition); writer.Write(overlapInfo.pushDirection); } } public override void Deserialize(NetworkReader reader) { base.Deserialize(reader); attacker = reader.ReadGameObject(); inflictor = reader.ReadGameObject(); damage = reader.ReadSingle(); isCrit = reader.ReadBoolean(); procChainMask = reader.ReadProcChainMask(); procCoefficient = reader.ReadSingle(); damageColorIndex = reader.ReadDamageColorIndex(); damageType = reader.ReadDamageType(); forceVector = reader.ReadVector3(); pushAwayForce = reader.ReadSingle(); overlapInfoList.Clear(); int i = 0; for (int num = (int)reader.ReadPackedUInt32(); i < num; i++) { OverlapInfo item = default(OverlapInfo); item.hurtBox = reader.ReadHurtBoxReference().ResolveGameObject()?.GetComponent(); item.hitPosition = reader.ReadVector3(); item.pushDirection = reader.ReadVector3(); overlapInfoList.Add(item); } } } public GameObject attacker; public GameObject inflictor; public TeamIndex teamIndex; public AttackerFiltering attackerFiltering = AttackerFiltering.NeverHitSelf; public Vector3 forceVector = Vector3.zero; public float pushAwayForce; public float damage = 1f; public bool isCrit; public ProcChainMask procChainMask; public float procCoefficient = 1f; public HitBoxGroup hitBoxGroup; public GameObject hitEffectPrefab; public NetworkSoundEventIndex impactSound = NetworkSoundEventIndex.Invalid; public DamageColorIndex damageColorIndex; public DamageTypeCombo damageType; public int maximumOverlapTargets = 100; private readonly List ignoredHealthComponentList = new List(); public float retriggerTimeout = float.PositiveInfinity; private Queue<(HealthComponent, float)> ignoredRemovalList = new Queue<(HealthComponent, float)>(); private readonly List overlapList = new List(); private static readonly OverlapAttackMessage incomingMessage = new OverlapAttackMessage(); private static readonly OverlapAttackMessage outgoingMessage = new OverlapAttackMessage(); public Vector3 lastFireAverageHitPosition { get; private set; } private bool HurtBoxPassesFilter(HurtBox hurtBox) { if (!hurtBox.healthComponent) { return true; } if (hurtBox.healthComponent.gameObject == attacker && attackerFiltering == AttackerFiltering.NeverHitSelf) { return false; } if (attacker == null && hurtBox.healthComponent.gameObject.GetComponent() != null) { return false; } if (ignoredHealthComponentList.Contains(hurtBox.healthComponent)) { return false; } if (!FriendlyFireManager.ShouldDirectHitProceed(hurtBox.healthComponent, teamIndex)) { return false; } return true; } public bool Fire(List hitResults = null) { if (!hitBoxGroup) { return false; } HitBox[] hitBoxes = hitBoxGroup.hitBoxes; foreach (HitBox hitBox in hitBoxes) { if (!hitBox || !hitBox.enabled || !hitBox.gameObject || !hitBox.gameObject.activeInHierarchy || !hitBox.transform) { continue; } Transform transform = hitBox.transform; Vector3 position = transform.position; Vector3 halfExtents = transform.lossyScale * 0.5f; Quaternion rotation = transform.rotation; if (float.IsInfinity(halfExtents.x) || float.IsInfinity(halfExtents.y) || float.IsInfinity(halfExtents.z)) { Chat.AddMessage("Aborting OverlapAttack.Fire: hitBoxHalfExtents are infinite."); continue; } if (float.IsNaN(halfExtents.x) || float.IsNaN(halfExtents.y) || float.IsNaN(halfExtents.z)) { Chat.AddMessage("Aborting OverlapAttack.Fire: hitBoxHalfExtents are NaN."); continue; } if (float.IsInfinity(position.x) || float.IsInfinity(position.y) || float.IsInfinity(position.z)) { Chat.AddMessage("Aborting OverlapAttack.Fire: hitBoxCenter is infinite."); continue; } if (float.IsNaN(position.x) || float.IsNaN(position.y) || float.IsNaN(position.z)) { Chat.AddMessage("Aborting OverlapAttack.Fire: hitBoxCenter is NaN."); continue; } if (float.IsInfinity(rotation.x) || float.IsInfinity(rotation.y) || float.IsInfinity(rotation.z) || float.IsInfinity(rotation.w)) { Chat.AddMessage("Aborting OverlapAttack.Fire: hitBoxRotation is infinite."); continue; } if (float.IsNaN(rotation.x) || float.IsNaN(rotation.y) || float.IsNaN(rotation.z) || float.IsNaN(rotation.w)) { Chat.AddMessage("Aborting OverlapAttack.Fire: hitBoxRotation is NaN."); continue; } Collider[] colliders; int num = HGPhysics.OverlapBox(out colliders, position, halfExtents, rotation, LayerIndex.entityPrecise.mask); cleanupRetriggerList(); int num2 = 0; for (int j = 0; j < num; j++) { if ((bool)colliders[j]) { HurtBox component = colliders[j].GetComponent(); if ((bool)component && HurtBoxPassesFilter(component) && (bool)component.transform) { Vector3 position2 = component.transform.position; overlapList.Add(new OverlapInfo { hurtBox = component, hitPosition = position2, pushDirection = (position2 - position).normalized }); addIgnoredHitList(component.healthComponent); hitResults?.Add(component); num2++; } if (num2 >= maximumOverlapTargets) { break; } } } HGPhysics.ReturnResults(colliders); } ProcessHits(overlapList); bool result = overlapList.Count > 0; overlapList.Clear(); return result; } public void addIgnoredHitList(HealthComponent component) { if (retriggerTimeout != float.PositiveInfinity) { ignoredRemovalList.Enqueue((component, Time.realtimeSinceStartup + retriggerTimeout)); } ignoredHealthComponentList.Add(component); } public void cleanupRetriggerList() { while (ignoredRemovalList.Count > 0 && Time.realtimeSinceStartup > ignoredRemovalList.Peek().Item2) { (HealthComponent, float) tuple = ignoredRemovalList.Dequeue(); ignoredHealthComponentList.Remove(tuple.Item1); } } [NetworkMessageHandler(msgType = 71, client = false, server = true)] public static void HandleOverlapAttackHits(NetworkMessage netMsg) { netMsg.ReadMessage(incomingMessage); PerformDamage(incomingMessage.attacker, incomingMessage.inflictor, incomingMessage.damage, incomingMessage.isCrit, incomingMessage.procChainMask, incomingMessage.procCoefficient, incomingMessage.damageColorIndex, incomingMessage.damageType, incomingMessage.forceVector, incomingMessage.pushAwayForce, incomingMessage.overlapInfoList); } private void ProcessHits(List hitList) { if (hitList.Count == 0) { return; } Vector3 zero = Vector3.zero; float num = 1f / (float)hitList.Count; for (int i = 0; i < hitList.Count; i++) { OverlapInfo overlapInfo = hitList[i]; if ((bool)hitEffectPrefab) { Vector3 forward = -hitList[i].pushDirection; EffectManager.SpawnEffect(hitEffectPrefab, new EffectData { origin = overlapInfo.hitPosition, rotation = Util.QuaternionSafeLookRotation(forward), networkSoundEventIndex = impactSound }, transmit: true); } zero += overlapInfo.hitPosition * num; SurfaceDefProvider surfaceDefProvider = (overlapInfo.hurtBox ? overlapInfo.hurtBox.GetComponent() : null); if (!surfaceDefProvider || !surfaceDefProvider.surfaceDef) { continue; } SurfaceDef objectSurfaceDef = SurfaceDefProvider.GetObjectSurfaceDef(hitList[i].hurtBox.collider, hitList[i].hitPosition); if ((bool)objectSurfaceDef) { if ((bool)objectSurfaceDef.impactEffectPrefab) { EffectManager.SpawnEffect(objectSurfaceDef.impactEffectPrefab, new EffectData { origin = overlapInfo.hitPosition, rotation = ((overlapInfo.pushDirection == Vector3.zero) ? Quaternion.identity : Util.QuaternionSafeLookRotation(overlapInfo.pushDirection)), color = objectSurfaceDef.approximateColor, scale = 2f }, transmit: true); } if (objectSurfaceDef.impactSoundString != null && objectSurfaceDef.impactSoundString.Length != 0) { Util.PlaySound(objectSurfaceDef.impactSoundString, hitList[i].hurtBox.gameObject); } } } lastFireAverageHitPosition = zero; if (NetworkServer.active) { PerformDamage(attacker, inflictor, damage, isCrit, procChainMask, procCoefficient, damageColorIndex, damageType, forceVector, pushAwayForce, hitList); return; } outgoingMessage.attacker = attacker; outgoingMessage.inflictor = inflictor; outgoingMessage.damage = damage; outgoingMessage.isCrit = isCrit; outgoingMessage.procChainMask = procChainMask; outgoingMessage.procCoefficient = procCoefficient; outgoingMessage.damageColorIndex = damageColorIndex; outgoingMessage.damageType = damageType; outgoingMessage.forceVector = forceVector; outgoingMessage.pushAwayForce = pushAwayForce; Util.CopyList(hitList, outgoingMessage.overlapInfoList); PlatformSystems.networkManager.client.connection.SendByChannel(71, outgoingMessage, QosChannelIndex.defaultReliable.intVal); } private static void PerformDamage(GameObject attacker, GameObject inflictor, float damage, bool isCrit, ProcChainMask procChainMask, float procCoefficient, DamageColorIndex damageColorIndex, DamageTypeCombo damageType, Vector3 forceVector, float pushAwayForce, List hitList) { for (int i = 0; i < hitList.Count; i++) { OverlapInfo overlapInfo = hitList[i]; if ((bool)overlapInfo.hurtBox) { HealthComponent healthComponent = overlapInfo.hurtBox.healthComponent; if ((bool)healthComponent) { DamageInfo damageInfo = new DamageInfo(); damageInfo.attacker = attacker; damageInfo.inflictor = inflictor; damageInfo.force = forceVector + pushAwayForce * overlapInfo.pushDirection; damageInfo.damage = damage; damageInfo.crit = isCrit; damageInfo.position = overlapInfo.hitPosition; damageInfo.procChainMask = procChainMask; damageInfo.procCoefficient = procCoefficient; damageInfo.damageColorIndex = damageColorIndex; damageInfo.damageType = damageType; damageInfo.ModifyDamageInfo(overlapInfo.hurtBox.damageModifier); healthComponent.TakeDamage(damageInfo); GlobalEventManager.instance.OnHitEnemy(damageInfo, healthComponent.gameObject); GlobalEventManager.instance.OnHitAll(damageInfo, healthComponent.gameObject); } } } } public void ResetIgnoredHealthComponents() { ignoredHealthComponentList.Clear(); ignoredRemovalList.Clear(); } public void Reset() { attacker = null; inflictor = null; teamIndex = TeamIndex.Neutral; forceVector = Vector3.zero; pushAwayForce = 0f; damage = 1f; isCrit = false; procChainMask.mask = 0u; procCoefficient = 1f; hitBoxGroup = null; hitEffectPrefab = null; damageColorIndex = DamageColorIndex.Default; damageType = DamageType.Generic; ignoredHealthComponentList.Clear(); ignoredRemovalList.Clear(); } }