Files
Project-M/Assets/_Project/Scripts/Simulation/Combat/MeleeConeMath.cs
T
kronic 3409c53148 Combat: MC-4 combo-chain melee as the primary verb (DR-030)
Melee combo (left-click / pad-West) becomes the player's primary verb; the ranged projectile is demoted to right-click / pad-left-trigger. Predicted, owner-replicated combo Step (path-dependent -> [GhostField] anchor + absolute-write idempotency, NOT derived like the dash), server-only cleave mirroring ProjectileDamageSystem (SourceTick-stamped DamageEvent + KnockbackState), dash-cancellable movement-commit, 9 live TuningConfig knobs, and swing juice scaling with the combo step. The MC-6 archetype byte is deferred (the melee is its own verb). See DR-030.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:22:57 -07:00

40 lines
2.0 KiB
C#

using Unity.Mathematics;
namespace ProjectM.Simulation
{
/// <summary>
/// Pure, deterministic cone hit-test for the MC-4 melee cleave. Shares the EXACT range + bearing predicate with
/// <see cref="AutoTarget"/> (planar XZ distance gate + <c>dot(bearing, facing) &gt;= cos(halfAngle)</c>) but as a
/// per-candidate boolean, so the cleave can collect ALL enemies in the cone — <see cref="AutoTarget.Resolve"/> is a
/// single-winner reducer and cannot be reused for collect-all (MC-4 review REUSE-1). Burst-safe, allocation-free,
/// no wall-clock / no randomness; intended to run server-side inside the predicted <c>MeleeComboSystem</c>.
/// </summary>
public static class MeleeConeMath
{
/// <summary>
/// True iff <paramref name="targetPos"/> lies within <paramref name="range"/> (planar XZ) of
/// <paramref name="from"/> AND its bearing from <paramref name="from"/> is within the cone half-angle of
/// <paramref name="facingDir"/> (<paramref name="cosHalfAngle"/> = cos(halfAngle)). A coincident
/// (zero-distance) target is excluded (undefined bearing). <paramref name="facingDir"/> must be
/// caller-normalized; a zero-length facing or a non-positive range returns false.
/// </summary>
public static bool InCone(float3 from, float2 facingDir, float range, float cosHalfAngle, float3 targetPos)
{
if (range <= 0f || math.lengthsq(facingDir) < 1e-6f)
return false;
float3 offset = targetPos - from;
float2 planar = new float2(offset.x, offset.z);
float distSq = math.lengthsq(planar);
if (distSq < 1e-6f)
return false; // coincident -> undefined bearing
if (distSq > range * range)
return false; // out of range
float2 bearing = planar * math.rsqrt(distSq); // normalized planar bearing
return math.dot(bearing, facingDir) >= cosHalfAngle;
}
}
}