using Unity.Mathematics;
namespace ProjectM.Simulation
{
///
/// Pure, deterministic Husk AI math — no RNG, no wall-clock — so server simulation stays reproducible and
/// the helpers are EditMode-unit-testable without an ECS world (mirrors /
/// StatMath).
///
public static class EnemyAIMath
{
///
/// Planar (XZ) seek velocity from toward at
/// . Y is forced to 0 (top-down plane). Returns zero once within
/// (so the Husk halts at strike range instead of jittering through the
/// target) or when the two points coincide.
///
public static float3 SeekVelocity(float3 from, float3 to, float speed, float stopDistance)
{
float3 d = to - from;
d.y = 0f;
float distSq = math.lengthsq(d);
if (distSq <= stopDistance * stopDistance || distSq < 1e-8f)
return float3.zero;
return math.normalize(d) * speed;
}
///
/// True when is within of on the
/// XZ plane.
///
public static bool InAttackRange(float3 from, float3 to, float range)
{
float3 d = to - from;
d.y = 0f;
return math.lengthsq(d) <= range * range;
}
///
/// Projects a planar movement onto a wall plane defined by
/// (collide-and-slide): removes the component of that pushes into the surface so the
/// mover glances along the wall instead of stopping dead. Both inputs are flattened to the XZ plane (top-down).
/// Returns unchanged when the normal is degenerate.
///
public static float3 SlideVelocity(float3 vel, float3 surfaceNormal)
{
surfaceNormal.y = 0f;
float len = math.length(surfaceNormal);
if (len < 1e-6f)
return vel;
float3 n = surfaceNormal / len;
float3 slid = vel - math.dot(vel, n) * n;
slid.y = 0f;
return slid;
}
///
/// Deterministic planar ring position around for spawn
/// : evenly spaced over angles at
/// . Stable per index so a replayed spawn lands identically.
///
public static float3 RingPosition(float3 center, int index, int slots, float radius)
{
if (slots < 1)
slots = 1;
int slot = ((index % slots) + slots) % slots;
float angle = (2f * math.PI * slot) / slots;
math.sincos(angle, out float s, out float c);
return center + new float3(c * radius, 0f, s * radius);
}
}
}