using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
namespace ProjectM.Simulation
{
///
/// Pure tick-window predicates over replicated combat state (MeleeCombo swing window, per-socket
/// cooldown-derived fire windows). Extracted VERBATIM from PlayerAnimationDriveSystem (MC-4/B7) so the
/// predicted facing path (PlayerAimSystem) and the client animation path share one implementation.
/// Window lengths stay CALLER parameters (the anim path passes its k_AttackAnimTicks; the facing path
/// passes its own) — never baked constants. Movement-archetype sockets (dash/blink) NEVER count as
/// firing: a blink is a dodge, not a cast (this also kills the latent cooldown-tail phantom IsFiring
/// pulse the old include-all check produced — the blink socket's stamped cooldown row used to open a
/// fake window ~2.5s after each blink). Burst-safe statics (byte archetypes, no enums), EditMode-tested.
///
public static class TickWindowMath
{
/// True while now is within [SwingStartTick, SwingStartTick + animTicks) — a per-swing pulse
/// that re-triggers on each chained swing. NetworkTick arithmetic (wrap-safe).
public static bool SwingActive(in MeleeCombo mc, NetworkTick serverTick, uint animTicks)
{
if (mc.SwingStartTick == 0u || !serverTick.IsValid) return false;
var start = new NetworkTick(mc.SwingStartTick);
var end = new NetworkTick(TickUtil.NonZero(mc.SwingStartTick + animTicks));
return start.IsValid && end.IsValid && !start.IsNewerThan(serverTick) && end.IsNewerThan(serverTick);
}
/// True while now is within the stateless fire window [NextFireTick - CooldownTicks, +animTicks)
/// reconstructed from the replicated per-socket cooldown stamp minus the locally-derived CooldownTicks
/// (recomputes identically on every world; no cached edges — edge-caches false-fire on
/// relevancy/join/rollback). Window start shifts if cooldown modifiers change mid-window — documented
/// acceptable (B7).
public static bool FireActive(uint nextFireRaw, int cooldownTicks, NetworkTick serverTick, uint animTicks)
{
if (nextFireRaw == 0u || cooldownTicks <= 0 || !serverTick.IsValid) return false;
uint startRaw = FireStartRaw(nextFireRaw, cooldownTicks);
var start = new NetworkTick(startRaw);
var end = new NetworkTick(TickUtil.NonZero(startRaw + animTicks));
return start.IsValid && end.IsValid && !start.IsNewerThan(serverTick) && end.IsNewerThan(serverTick);
}
/// Reconstructed fire-window START (raw NonZero tick) from the replicated per-socket cooldown
/// stamp minus the locally-derived CooldownTicks; 0 = no window (unstamped / degenerate inputs). The ONE
/// home of this reconstruction (07-21 G6 review wf_98bf1268: the cone connect-cue latches
/// contact = FireStartRaw + ConeContactTicks at the fire edge — never re-derive it inline).
public static uint FireStartRaw(uint nextFireRaw, int cooldownTicks)
{
if (nextFireRaw == 0u || cooldownTicks <= 0) return 0u;
return TickUtil.NonZero(nextFireRaw - (uint)cooldownTicks);
}
/// LANTERN 4-socket fire/cone resolution (any-socket model): firing if ANY socketed,
/// NON-Movement Spark's per-socket window is mid-fire; cone if any such active socket holds a
/// Cone-archetype Spark. Movement sockets are skipped OUTRIGHT (blink = dodge, not cast). Without a
/// blob the archetype is unknown — the socket is included (matches the old fallback behavior).
public static void SocketFireAndCone(in SocketCooldown cd, DynamicBuffer sockets,
DynamicBuffer effSockets, BlobAssetReference abilityDb,
NetworkTick serverTick, uint animTicks, out bool firing, out bool cone)
{
firing = false; cone = false;
int n = math.min(SocketId.Count, math.min(sockets.Length, effSockets.Length));
bool haveDb = abilityDb.IsCreated;
for (int sk = 0; sk < n; sk++)
{
byte sid = sockets[sk].SparkId;
if (sid == 0) continue;
bool isCone = false;
if (haveDb)
{
ref var adb = ref abilityDb.Value;
if (adb.TryGetAbility(sid, out var d))
{
if (d.Archetype == (byte)AbilityArchetype.Movement) continue; // dodge, never a cast
isCone = d.Archetype == (byte)AbilityArchetype.Cone;
}
}
if (!FireActive(cd.Get(sk), effSockets[sk].CooldownTicks, serverTick, animTicks)) continue;
firing = true;
if (isCone) cone = true;
}
}
}
}