// Assets/_Project/Scripts/Gameplay/BuilderSpells/BuilderSpellDefinition.cs
using UnityEngine;
using TD.Core;
namespace TD.Gameplay.BuilderSpells
{
///
/// Base class for one builder spell — a player-cast, key-triggered ability (e.g. "drop a
/// fireball on a point", "slow enemies in a radius"). Granted via the draft () and tracked per-player by , which owns the hotkey slot and cooldown state.
///
///
/// Contrast with . Effects
/// are passive and queried by consumers; spells are active and resolve themselves.
/// is that resolution — it IS the "apply the effect" method,
/// replacing the effect system's IsValidFor-only gate pattern (grant-time validity
/// still lives on ).
///
/// One asset per kind. is fixed per subclass, same as
/// . uses
/// it to build a fixed-size, enum-indexed lookup table.
///
/// Server-only resolution, client-only visual. runs on
/// the server and applies damage/status via the same Physics.OverlapSphereNonAlloc +
/// EnemyHealth/EnemyStatus pattern TowerCombat already uses.
/// runs on every peer (including the server) after a successful
/// cast, purely for presentation.
///
public abstract class BuilderSpellDefinition : ScriptableObject
{
/// Which builder spell this asset's data belongs to.
public abstract BuilderSpellKind Kind { get; }
[Header("Presentation")]
[Tooltip("Name shown on the draft card and the cast hotbar.")]
public string DisplayName;
[Tooltip("Short description shown on the draft card.")]
[TextArea(2, 4)]
public string Description;
[Tooltip("Icon shown on the draft card and the cast hotbar.")]
public Sprite Icon;
[Header("Casting")]
[Tooltip("Seconds before this spell can be cast again after a successful cast.")]
[Min(0f)]
public float Cooldown = 5f;
[Tooltip("How the cast controller aims this spell. AreaOfEffect previews Radius as a " +
"decal while aiming; PointTarget does not, even if Radius is used internally " +
"(e.g. a splash radius).")]
public SpellTargetType TargetType;
[Tooltip("AreaOfEffect: the resolution radius AND the client-side preview size. " +
"PointTarget: optional internal-only radius (e.g. splash) with no preview.")]
[Min(0f)]
public float Radius;
[Tooltip("Physics layer(s) enemies occupy, queried by this spell's own OverlapSphere " +
"call. Each spell asset authors its own mask — same convention as " +
"TowerCombat/Projectile's per-instance enemyLayerMask.")]
[SerializeField]
protected LayerMask enemyLayerMask;
// Shared scratch buffer for OverlapSphereNonAlloc queries. The server processes casts
// sequentially (one RPC handler at a time), so a static buffer shared across all spell
// assets is safe — mirrors TowerCombat.s_overlapBuffer, but sized larger (64 vs. 32):
// OverlapSphereNonAlloc silently truncates past the buffer length with no way to
// detect the truncation, and spells are more likely than a single tower's splash/chain
// radius to catch a dense horde. TowerCombat's buffer is intentionally left at 32 —
// out of scope here, flagged separately for the team to revisit.
protected static readonly Collider[] s_overlapBuffer = new Collider[64];
///
/// Server-only: resolve this spell's effect at for the
/// casting player. Returns false if the cast could not be applied (e.g. hit nothing) —
/// treats a false return as a no-op and does not start
/// the cooldown.
///
public abstract bool ServerCast(ulong clientId, Vector3 targetPoint);
///
/// Runs on every peer (via 's ClientRpc) after a
/// successful . Default no-op; override to spawn an impact VFX
/// prefab and self-destroy it, the same idiom used elsewhere for one-off visuals.
///
public virtual void ClientPlayVfx(Vector3 targetPoint) { }
}
}