Entity/HurtShapes/HurtArc.cs
using System;
using Sandbox;
namespace MANIFOLD.BHLib.Components {
[Title("Arc")]
[Category("Hurt Shapes")]
[Icon("signal_wifi_0_bar")]
public class HurtArcDefinition : HurtComponentDefinition {
[Range(0f, 360f), Space]
public float Angle { get; set; }
public bool Centered { get; set; } = true;
[Space]
public float EndDistance { get; set; }
public float Thickness { get; set; }
public float DistanceVelocity { get; set; }
public override EntityComponent Create(GameObject obj) {
var comp = obj.AddComponent<HurtArc>();
comp.Data = this;
return comp;
}
}
/// <summary>
/// Hurts targets within in arc.
/// </summary>
[Category(LibraryData.CATEGORY + "/Hurt Shapes")]
[Icon("signal_wifi_0_bar")]
[Hide]
public class HurtArc : EntityComponent {
public const float MIN_THICKNESS = 0.1f;
private bool triedRender;
private ArcRenderer renderer;
private float additive;
public HurtArcDefinition Data { get; set; }
public float RealEndDistance => Data.EndDistance + additive;
protected override void OnFixedUpdate() {
if (!triedRender) {
TryGetRenderer();
triedRender = true;
}
base.OnFixedUpdate();
}
public override void SimulateFrame(float deltaTime) {
base.SimulateFrame(deltaTime);
additive += Data.DistanceVelocity * deltaTime;
if (Target.IsValid()) CheckPhysics();
if (renderer.IsValid()) UpdateModel();
}
public override void Simulate(float time) {
additive = 0;
base.Simulate(time);
}
protected override void DrawGizmos() {
float rads = Data.Angle.DegreeToRadian();
float startRads = Data.Centered ? rads * -0.5f : 0;
float endRads = Data.Centered ? rads * 0.5f : rads;
Vector3 startDir = new Vector3(MathF.Cos(startRads), MathF.Sin(startRads), 0f);
Vector3 endDir = new Vector3(MathF.Cos(endRads), MathF.Sin(endRads), 0f);
float startDistance = MathF.Max(RealEndDistance - Data.Thickness, 0);
Gizmo.Draw.Color = Color.Red;
Gizmo.Draw.Line(startDir * startDistance, startDir * RealEndDistance);
Gizmo.Draw.Line(endDir * startDistance, endDir * RealEndDistance);
DrawArc(startRads, endRads, RealEndDistance);
if (Data.Thickness > 0 && !Data.Thickness.AlmostEqual(0)) {
if (startDistance > 0) {
DrawArc(startRads, endRads, startDistance);
Gizmo.Draw.Color = Color.White;
Gizmo.Draw.Line(0, Vector3.Forward * startDistance);
}
}
}
private void TryGetRenderer() {
var indirect = GetComponent<Renderer>();
if (indirect.IsValid()) {
renderer = indirect.Held.GetComponent<ArcRenderer>();
}
}
private void CheckPhysics() {
Vector3 toPlayer = Target.WorldPosition - WorldPosition;
Vector3 localToPlayer = WorldRotation.Inverse * toPlayer;
float startDistance = MathF.Max(RealEndDistance - Data.Thickness, 0);
float realThickness = RealEndDistance - startDistance;
float halfThickness = realThickness * 0.5f;
float sampleLength = RealEndDistance.LerpTo(startDistance, 0.5f);
float minAngle = (Data.Centered ? Data.Angle * -0.5f : 0).DegreeToRadian();
float maxAngle = (Data.Centered ? Data.Angle * 0.5f : Data.Angle).DegreeToRadian();
float circumference = 2 * MathF.PI * sampleLength;
float posSoftMargin = (halfThickness / circumference) * MathF.PI * 2;
float posHardMargin = (MIN_THICKNESS / circumference) * MathF.PI * 2;
var softEdges = GetMargins(minAngle, maxAngle, posSoftMargin);
var hardEdges = GetMargins(minAngle, maxAngle, posHardMargin);
float rawRads = MathF.Atan2(localToPlayer.y, localToPlayer.x);
float posRads = rawRads.Clamp(hardEdges.left, hardEdges.right);
float rotRads = rawRads.Clamp(minAngle, maxAngle);
float sizeFactor = posRads.LerpInverse(hardEdges.left, softEdges.left) * posRads.LerpInverse(hardEdges.right, softEdges.right);
Vector3 sampleDir = new Vector3(MathF.Cos(posRads), MathF.Sin(posRads), 0f);
Vector3 samplePos = WorldTransform.PointToWorld(sampleDir * sampleLength);
Rotation sampleRot = new Angles(0f, rotRads.RadianToDegree(), 0f);
Vector3 sampleSize = realThickness;
sampleSize.y = MathF.Min(sampleSize.y, (Data.Angle / 360) * circumference);
sampleSize.y = sampleSize.y.LerpTo(MIN_THICKNESS, 1 - sizeFactor);
var result = Scene.Trace.Box(sampleSize, samplePos, samplePos).Rotated(sampleRot).WithTag("player").Run();
var wasTarget = CheckForTarget(result);
if (wasTarget) {
var successful = Target.Hurt(new DamageInfo() {
attacker = GameObject,
damage = Data.Damage,
impulseDirection = (Target.WorldPosition - samplePos).WithZ(0).Normal
});
if (successful && Data.DestroyOnHurt) {
GameObject.Destroy();
}
}
// DebugOverlay.Box(0, Thickness, Color.Red, Time.Delta, new Transform(samplePos, WorldRotation * sampleRot));
DebugOverlay.Sphere(new Sphere(WorldPosition + (sampleDir * sampleLength), 8), Color.Blue, Time.Delta);
DebugOverlay.Trace(result);
}
private void UpdateModel() {
renderer.Angle = Data.Angle;
renderer.Centered = Data.Centered;
renderer.StartLength = RealEndDistance - Data.Thickness;
renderer.EndLength = RealEndDistance;
}
private (float left, float right) GetMargins(float left, float right, float margin) {
return (MathF.Min(left + margin, Data.Centered ? 0 : margin), MathF.Max(right - margin, Data.Centered ? 0 : -margin));
}
protected void DrawArc(float startRads, float endRads, float distance, int resolution = 24) {
Vector3 prevPoint = new Vector3(MathF.Cos(startRads), MathF.Sin(startRads), 0f) * distance;
for (int i = 0; i < resolution; i++) {
float factor = (i + 1) / (float)resolution;
float rads = startRads.LerpTo(endRads, factor);
Vector3 nextPoint = new Vector3(MathF.Cos(rads), MathF.Sin(rads), 0f) * distance;
Gizmo.Draw.Line(prevPoint, nextPoint);
prevPoint = nextPoint;
}
}
}
}