using System; using HG; using RoR2.Networking; using Unity.Collections; using UnityEngine; using UnityEngine.Networking; namespace RoR2; public class BlastAttack { public enum FalloffModel { None, Linear, SweetSpot } public enum LoSType { None, NearestHit } public struct HitPoint { public HurtBox hurtBox; public Vector3 hitPosition; public Vector3 hitNormal; public float distanceSqr; } public struct Result { public int hitCount; public HitPoint[] hitPoints; } private struct BlastAttackDamageInfo { public GameObject attacker; public GameObject inflictor; public bool crit; public float damage; public DamageColorIndex damageColorIndex; public HurtBox.DamageModifier damageModifier; public DamageTypeCombo damageType; public Vector3 force; public Vector3 position; public ProcChainMask procChainMask; public float procCoefficient; public HealthComponent hitHealthComponent; public bool canRejectForce; public void Write(NetworkWriter writer) { writer.Write(attacker); writer.Write(inflictor); writer.Write(crit); writer.Write(damage); writer.Write(damageColorIndex); writer.Write((byte)damageModifier); writer.WriteDamageType(damageType); writer.Write(force); writer.Write(position); writer.Write(procChainMask); writer.Write(procCoefficient); writer.Write(hitHealthComponent.netId); writer.Write(canRejectForce); } public void Read(NetworkReader reader) { attacker = reader.ReadGameObject(); inflictor = reader.ReadGameObject(); crit = reader.ReadBoolean(); damage = reader.ReadSingle(); damageColorIndex = reader.ReadDamageColorIndex(); damageModifier = (HurtBox.DamageModifier)reader.ReadByte(); damageType = reader.ReadDamageType(); force = reader.ReadVector3(); position = reader.ReadVector3(); procChainMask = reader.ReadProcChainMask(); procCoefficient = reader.ReadSingle(); GameObject gameObject = reader.ReadGameObject(); hitHealthComponent = (gameObject ? gameObject.GetComponent() : null); canRejectForce = reader.ReadBoolean(); } } public GameObject attacker; public GameObject inflictor; public TeamIndex teamIndex; public AttackerFiltering attackerFiltering; public Vector3 position; public float radius; public FalloffModel falloffModel = FalloffModel.Linear; public float baseDamage; public float baseForce; public Vector3 bonusForce; public bool crit; public DamageTypeCombo damageType = DamageType.Generic; public DamageColorIndex damageColorIndex; public LoSType losType; public EffectIndex impactEffect = EffectIndex.Invalid; public bool canRejectForce = true; public ProcChainMask procChainMask; public float procCoefficient = 1f; private static readonly int initialBufferSize = 256; private static HitPoint[] hitPointsBuffer = new HitPoint[initialBufferSize]; private static int[] hitOrderBuffer = new int[initialBufferSize]; private static HealthComponent[] encounteredHealthComponentsBuffer = new HealthComponent[initialBufferSize]; public static event Action BlastAttackMultiHit; public Result Fire() { HitPoint[] array = CollectHits(); HandleHits(array); Result result = default(Result); result.hitCount = array.Length; result.hitPoints = array; return result; } [NetworkMessageHandler(msgType = 75, client = false, server = true)] private static void HandleReportBlastAttackDamage(NetworkMessage netMsg) { NetworkReader reader = netMsg.reader; BlastAttackDamageInfo blastAttackDamageInfo = default(BlastAttackDamageInfo); blastAttackDamageInfo.Read(reader); PerformDamageServer(in blastAttackDamageInfo); } public void Reset() { attacker = null; inflictor = null; teamIndex = TeamIndex.Neutral; position = Vector3.zero; radius = 0f; falloffModel = FalloffModel.Linear; baseDamage = 0f; baseForce = 0f; bonusForce = Vector3.zero; crit = false; damageType = DamageType.Generic; damageColorIndex = DamageColorIndex.Default; procChainMask.mask = 0u; procCoefficient = 1f; canRejectForce = true; } private HitPoint[] CollectHits() { Vector3 vector = position; Collider[] array = Physics.OverlapSphere(vector, radius, LayerIndex.entityPrecise.mask); int num = array.Length; int count = 0; int encounteredHealthComponentsLength = 0; int hitOrderBufferLength = 0; ArrayUtils.EnsureCapacity(ref hitPointsBuffer, num); ArrayUtils.EnsureCapacity(ref hitOrderBuffer, num); ArrayUtils.EnsureCapacity(ref encounteredHealthComponentsBuffer, num); for (int i = 0; i < num; i++) { Collider collider = array[i]; HurtBox component = collider.GetComponent(); if (!component) { continue; } HealthComponent healthComponent2 = component.healthComponent; if (!healthComponent2) { continue; } bool flag = true; switch (attackerFiltering) { case AttackerFiltering.Default: flag = true; break; case AttackerFiltering.AlwaysHitSelf: flag = true; if (healthComponent2.gameObject == attacker) { flag = false; } break; case AttackerFiltering.AlwaysHit: flag = false; break; case AttackerFiltering.NeverHitSelf: flag = true; if (healthComponent2.gameObject == attacker) { continue; } break; } if (!flag || FriendlyFireManager.ShouldSplashHitProceed(healthComponent2, teamIndex)) { Vector3 vector2 = collider.transform.position; Vector3 hitNormal = vector - vector2; float sqrMagnitude = hitNormal.sqrMagnitude; hitPointsBuffer[count++] = new HitPoint { hurtBox = component, hitPosition = vector2, hitNormal = hitNormal, distanceSqr = sqrMagnitude }; } } if (true) { for (int j = 0; j < count; j++) { ref HitPoint reference = ref hitPointsBuffer[j]; if ((object)reference.hurtBox != null && reference.distanceSqr > 0f && reference.hurtBox.collider.Raycast(new Ray(vector, -reference.hitNormal), out var hitInfo, radius)) { reference.distanceSqr = (vector - hitInfo.point).sqrMagnitude; reference.hitPosition = hitInfo.point; reference.hitNormal = hitInfo.normal; } } } hitOrderBufferLength = count; for (int k = 0; k < count; k++) { hitOrderBuffer[k] = k; } for (int l = 1; l < count; l++) { int num2 = l; while (num2 > 0 && hitPointsBuffer[hitOrderBuffer[num2 - 1]].distanceSqr > hitPointsBuffer[hitOrderBuffer[num2]].distanceSqr) { int num3 = hitOrderBuffer[num2 - 1]; hitOrderBuffer[num2 - 1] = hitOrderBuffer[num2]; hitOrderBuffer[num2] = num3; num2--; } } bool flag2 = losType == LoSType.None || losType == LoSType.NearestHit; for (int m = 0; m < hitOrderBufferLength; m++) { int num4 = hitOrderBuffer[m]; ref HitPoint reference2 = ref hitPointsBuffer[num4]; HealthComponent healthComponent3 = reference2.hurtBox.healthComponent; if (!EntityIsMarkedEncountered(healthComponent3)) { MarkEntityAsEncountered(healthComponent3); } else if (flag2) { reference2.hurtBox = null; } } ClearEncounteredEntities(); CondenseHitOrderBuffer(); LoSType loSType = losType; if (loSType != 0 && loSType == LoSType.NearestHit) { NativeArray commands = new NativeArray(hitOrderBufferLength, Allocator.TempJob); NativeArray results = new NativeArray(hitOrderBufferLength, Allocator.TempJob); int n = 0; int num5 = 0; for (; n < hitOrderBufferLength; n++) { int num6 = hitOrderBuffer[n]; ref HitPoint reference3 = ref hitPointsBuffer[num6]; commands[num5++] = new RaycastCommand(vector, reference3.hitPosition - vector, Mathf.Sqrt(reference3.distanceSqr), LayerIndex.world.mask); } bool queriesHitTriggers = Physics.queriesHitTriggers; Physics.queriesHitTriggers = true; RaycastCommand.ScheduleBatch(commands, results, 1).Complete(); Physics.queriesHitTriggers = queriesHitTriggers; int num7 = 0; int num8 = 0; for (; num7 < hitOrderBufferLength; num7++) { int num9 = hitOrderBuffer[num7]; ref HitPoint reference4 = ref hitPointsBuffer[num9]; if ((object)reference4.hurtBox != null && (bool)results[num8++].collider) { reference4.hurtBox = null; } } results.Dispose(); commands.Dispose(); CondenseHitOrderBuffer(); } HitPoint[] array2 = new HitPoint[hitOrderBufferLength]; for (int num10 = 0; num10 < hitOrderBufferLength; num10++) { int num11 = hitOrderBuffer[num10]; array2[num10] = hitPointsBuffer[num11]; } ArrayUtils.Clear(hitPointsBuffer, ref count); ClearEncounteredEntities(); return array2; void ClearEncounteredEntities() { Array.Clear(encounteredHealthComponentsBuffer, 0, encounteredHealthComponentsLength); encounteredHealthComponentsLength = 0; } void CondenseHitOrderBuffer() { for (int num12 = 0; num12 < hitOrderBufferLength; num12++) { int num13 = 0; for (int num14 = num12; num14 < hitOrderBufferLength; num14++) { int num15 = hitOrderBuffer[num14]; if ((object)hitPointsBuffer[num15].hurtBox != null) { break; } num13++; } if (num13 > 0) { ArrayUtils.ArrayRemoveAt(hitOrderBuffer, ref hitOrderBufferLength, num12, num13); } } } bool EntityIsMarkedEncountered(HealthComponent healthComponent) { for (int num16 = 0; num16 < encounteredHealthComponentsLength; num16++) { if ((object)encounteredHealthComponentsBuffer[num16] == healthComponent) { return true; } } return false; } void MarkEntityAsEncountered(HealthComponent healthComponent) { encounteredHealthComponentsBuffer[encounteredHealthComponentsLength++] = healthComponent; } } private void HandleHits(HitPoint[] hitPoints) { Vector3 vector = position; for (int i = 0; i < hitPoints.Length; i++) { HitPoint hitPoint = hitPoints[i]; float num = Mathf.Sqrt(hitPoint.distanceSqr); float num2 = 0f; Vector3 vector2 = ((num > 0f) ? ((hitPoint.hitPosition - vector) / num) : Vector3.zero); HealthComponent healthComponent = (hitPoint.hurtBox ? hitPoint.hurtBox.healthComponent : null); if ((bool)healthComponent) { switch (falloffModel) { case FalloffModel.None: num2 = 1f; break; case FalloffModel.Linear: num2 = 1f - Mathf.Clamp01(num / radius); break; case FalloffModel.SweetSpot: num2 = 1f - ((num > radius / 2f) ? 0.75f : 0f); break; } BlastAttackDamageInfo blastAttackDamageInfo = default(BlastAttackDamageInfo); blastAttackDamageInfo.attacker = attacker; blastAttackDamageInfo.inflictor = inflictor; blastAttackDamageInfo.crit = crit; blastAttackDamageInfo.damage = baseDamage * num2; blastAttackDamageInfo.damageColorIndex = damageColorIndex; blastAttackDamageInfo.damageModifier = hitPoint.hurtBox.damageModifier; blastAttackDamageInfo.damageType = damageType | DamageType.AOE; blastAttackDamageInfo.force = bonusForce * num2 + baseForce * num2 * vector2; blastAttackDamageInfo.position = hitPoint.hitPosition; blastAttackDamageInfo.procChainMask = procChainMask; blastAttackDamageInfo.procCoefficient = procCoefficient; blastAttackDamageInfo.hitHealthComponent = healthComponent; blastAttackDamageInfo.canRejectForce = canRejectForce; BlastAttackDamageInfo blastAttackDamageInfo2 = blastAttackDamageInfo; if (NetworkServer.active) { PerformDamageServer(in blastAttackDamageInfo2); } else { ClientReportDamage(in blastAttackDamageInfo2); } if (impactEffect != EffectIndex.Invalid) { EffectData effectData = new EffectData(); effectData.origin = hitPoint.hitPosition; effectData.rotation = Quaternion.LookRotation(-vector2); EffectManager.SpawnEffect(impactEffect, effectData, transmit: true); } } } BlastAttack.BlastAttackMultiHit?.Invoke(hitPoints, attacker); } private static void ClientReportDamage(in BlastAttackDamageInfo blastAttackDamageInfo) { NetworkWriter networkWriter = new NetworkWriter(); networkWriter.StartMessage(75); blastAttackDamageInfo.Write(networkWriter); networkWriter.FinishMessage(); PlatformSystems.networkManager.client.connection.SendWriter(networkWriter, QosChannelIndex.defaultReliable.intVal); } private static void PerformDamageServer(in BlastAttackDamageInfo blastAttackDamageInfo) { if ((bool)blastAttackDamageInfo.hitHealthComponent) { DamageInfo damageInfo = new DamageInfo(); damageInfo.attacker = blastAttackDamageInfo.attacker; damageInfo.inflictor = blastAttackDamageInfo.inflictor; damageInfo.damage = blastAttackDamageInfo.damage; damageInfo.crit = blastAttackDamageInfo.crit; damageInfo.force = blastAttackDamageInfo.force; damageInfo.procChainMask = blastAttackDamageInfo.procChainMask; damageInfo.procCoefficient = blastAttackDamageInfo.procCoefficient; damageInfo.damageType = blastAttackDamageInfo.damageType; damageInfo.damageColorIndex = blastAttackDamageInfo.damageColorIndex; damageInfo.position = blastAttackDamageInfo.position; damageInfo.canRejectForce = blastAttackDamageInfo.canRejectForce; damageInfo.ModifyDamageInfo(blastAttackDamageInfo.damageModifier); blastAttackDamageInfo.hitHealthComponent.TakeDamage(damageInfo); GlobalEventManager.instance.OnHitEnemy(damageInfo, blastAttackDamageInfo.hitHealthComponent.gameObject); GlobalEventManager.instance.OnHitAll(damageInfo, blastAttackDamageInfo.hitHealthComponent.gameObject); } } }