411 lines
12 KiB
C#
411 lines
12 KiB
C#
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<OverlapInfo> overlapInfoList = new List<OverlapInfo>();
|
|
|
|
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<HurtBox>();
|
|
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<HealthComponent> ignoredHealthComponentList = new List<HealthComponent>();
|
|
|
|
public float retriggerTimeout = float.PositiveInfinity;
|
|
|
|
private Queue<(HealthComponent, float)> ignoredRemovalList = new Queue<(HealthComponent, float)>();
|
|
|
|
private readonly List<OverlapInfo> overlapList = new List<OverlapInfo>();
|
|
|
|
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<MaulingRock>() != null)
|
|
{
|
|
return false;
|
|
}
|
|
if (ignoredHealthComponentList.Contains(hurtBox.healthComponent))
|
|
{
|
|
return false;
|
|
}
|
|
if (!FriendlyFireManager.ShouldDirectHitProceed(hurtBox.healthComponent, teamIndex))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool Fire(List<HurtBox> 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<HurtBox>();
|
|
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<OverlapInfo> 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<SurfaceDefProvider>() : 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<OverlapInfo> 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();
|
|
}
|
|
}
|