// Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaSpellDefinition.cs using UnityEngine; using TD.Audio; using TD.Core; namespace TD.Gameplay.BuilderSpells { /// /// Builder spell #2 — slow every enemy within an area for a duration. /// /// /// is /// — the cast controller previews as a decal /// while aiming, and this spell resolves against that same radius (no separate field). /// /// Zone, not a snapshot. spawns a that keeps re-checking the area for /// seconds, so only enemies currently standing inside the radius are ever slowed — walking /// out drops the slow almost immediately. Always succeeds and starts the cooldown, even if /// no enemy is in the radius at the moment of cast, since the zone can still catch one later. /// /// [CreateAssetMenu(fileName = "SlowAreaSpell", menuName = "TD/Builder Spells/Slow Area")] public class SlowAreaSpellDefinition : BuilderSpellDefinition { public override BuilderSpellKind Kind => BuilderSpellKind.SlowArea; [Header("Area VFX")] [Tooltip("Prefab spawned at the target point on a successful cast, authored at a 1-unit " + "radius — scaled up to match Radius here. Lives for EffectDuration, matching " + "how long the slow itself lasts. Optional — leave empty for no visual.")] [SerializeField] private GameObject areaVfxPrefab; [Tooltip("Sound played on every peer when this spell resolves successfully.")] [SerializeField] private SoundConfig areaSound; [Header("Slow Area")] [Tooltip("Speed multiplier applied to affected enemies for EffectDuration " + "(e.g. 0.5 = half speed).")] [Range(0f, 1f)] public float SlowFactor = 0.5f; [Tooltip("Seconds the zone itself persists. Enemies are only slowed while standing " + "inside it — walking out drops the slow almost immediately.")] [Min(0f)] public float EffectDuration = 3f; public override bool ServerCast(ulong clientId, Vector3 targetPoint) { PlayerSlot owner = PlayerMatchState.SlotForClient(clientId); var zoneObject = new GameObject("SlowZone (server)"); zoneObject.AddComponent().Initialize( targetPoint, Mathf.Max(Radius, 0.01f), SlowFactor, EffectDuration, enemyLayerMask, owner); // Always succeeds — placing the zone starts the cooldown even if no enemy is // standing in it yet, since it keeps slowing whoever walks in for EffectDuration. return true; } public override void ClientSpawnVfx(Vector3 targetPoint) { if (areaVfxPrefab != null) { var instance = Instantiate(areaVfxPrefab, targetPoint, Quaternion.identity); instance.transform.localScale *= Mathf.Max(Radius, 0.01f); Destroy(instance, EffectDuration); } } public override void ClientPlayImpact(Vector3 targetPoint) { // Instant spell (ImpactDelay = 0), so this fires on cast alongside ClientSpawnVfx. if (areaSound.clip != null) AudioManager.Instance?.Play(areaSound.clip, AudioCategory.Combat, areaSound.RandomPitch(), areaSound.volume); } } }