// Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowZoneRuntime.cs
using UnityEngine;
using TD.Core;
namespace TD.Gameplay.BuilderSpells
{
///
/// Server-only runtime object for a Slow Area cast. Re-scans its radius every frame for the
/// rest of the zone's lifetime, continuously refreshing the Cold status on whatever enemies
/// currently overlap it.
///
///
/// Zone, not a snapshot. Replaces 's old
/// single OverlapSphereNonAlloc call at cast time, which slowed whoever was standing
/// there for a fixed duration regardless of whether they walked out. This ticks continuously
/// instead, so only enemies currently inside the radius are ever affected.
///
/// Refresh, not enter/exit events. Each tick re-applies
/// with a short fixed window (),
/// not the zone's remaining lifetime. An enemy that walks out of the zone simply stops being
/// refreshed and the slow expires on its own seconds later —
/// no explicit exit detection needed.
///
/// Not a NetworkObject. It only ever runs where runs (the server), and it never needs to exist
/// on clients — the zone's visual is spawned independently on every peer via
/// .
///
public class SlowZoneRuntime : MonoBehaviour
{
// Comfortably longer than one frame so a single missed tick doesn't let the slow
// visibly drop while an enemy is still inside, but short enough that leaving the
// zone reads as an almost-immediate loss of the slow.
private const float RefreshWindow = 0.35f;
private static readonly Collider[] s_overlapBuffer = new Collider[64];
private float radius;
private float slowFactor;
private float remainingLifetime;
private LayerMask enemyLayerMask;
private PlayerSlot owner;
/// Positions and configures the zone. Call immediately after AddComponent.
public void Initialize(Vector3 position, float radius, float slowFactor, float lifetime,
LayerMask enemyLayerMask, PlayerSlot owner)
{
transform.position = position;
this.radius = radius;
this.slowFactor = slowFactor;
this.remainingLifetime = lifetime;
this.enemyLayerMask = enemyLayerMask;
this.owner = owner;
}
private void Update()
{
remainingLifetime -= Time.deltaTime;
if (remainingLifetime <= 0f)
{
Destroy(gameObject);
return;
}
int count = Physics.OverlapSphereNonAlloc(
transform.position, radius, s_overlapBuffer, enemyLayerMask);
for (int i = 0; i < count; i++)
{
var enemyHealth = s_overlapBuffer[i].GetComponent();
if (enemyHealth == null || enemyHealth.IsDead) continue;
var enemyStatus = s_overlapBuffer[i].GetComponent();
if (enemyStatus == null) continue;
enemyStatus.ApplyEffect(DamageType.Cold, slowFactor, RefreshWindow, owner);
}
}
}
}