85 lines
3.6 KiB
C#
85 lines
3.6 KiB
C#
// Assets/_Project/Scripts/Gameplay/BuilderSpells/FireballSpellDefinition.cs
|
|
using UnityEngine;
|
|
using TD.Audio;
|
|
using TD.Core;
|
|
|
|
namespace TD.Gameplay.BuilderSpells
|
|
{
|
|
/// <summary>
|
|
/// Builder spell #1 — drop a fireball on a point, dealing direct damage to whatever's
|
|
/// there plus splash damage to nearby enemies.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <see cref="BuilderSpellDefinition.TargetType"/> is <see cref="SpellTargetType.PointTarget"/>
|
|
/// — the cast controller shows only a fixed-size reticule, even though
|
|
/// <see cref="BuilderSpellDefinition.Radius"/> is still used internally here as the splash
|
|
/// radius (0 = no splash, direct hit only).
|
|
/// </remarks>
|
|
[CreateAssetMenu(fileName = "FireballSpell", menuName = "TD/Builder Spells/Fireball")]
|
|
public class FireballSpellDefinition : BuilderSpellDefinition
|
|
{
|
|
public override BuilderSpellKind Kind => BuilderSpellKind.Fireball;
|
|
|
|
[Header("Impact VFX")]
|
|
[Tooltip("Prefab spawned at the target point on a successful cast. Optional — leave empty for no visual.")]
|
|
[SerializeField] private GameObject impactVfxPrefab;
|
|
|
|
[Tooltip("Seconds before the spawned VFX instance is destroyed.")]
|
|
[Min(0f)]
|
|
[SerializeField] private float impactVfxLifetime = 2f;
|
|
|
|
[Tooltip("Seconds from cast until the falling fireball reaches the ground. Damage and the " +
|
|
"impact sound are held until then so they land with the visual — SET THIS TO MATCH " +
|
|
"the meteor VFX's fall time. 0 = everything resolves instantly on cast.")]
|
|
[Min(0f)]
|
|
[SerializeField] private float impactDelay = 0.8f;
|
|
|
|
public override float ImpactDelay => impactDelay;
|
|
|
|
[Header("Impact Sound")]
|
|
[Tooltip("Sound played on every peer when this spell resolves successfully. Same " +
|
|
"SoundConfig struct TowerBuiltSound uses — reuse a tower's clip directly if " +
|
|
"you want the same sound.")]
|
|
[SerializeField] private SoundConfig impactSound;
|
|
|
|
[Header("Fireball")]
|
|
[Tooltip("Damage dealt to every enemy within Radius of the target point.")]
|
|
[Min(0f)]
|
|
public float Damage = 50f;
|
|
|
|
public override bool ServerCast(ulong clientId, Vector3 targetPoint)
|
|
{
|
|
PlayerSlot owner = PlayerMatchState.SlotForClient(clientId);
|
|
|
|
int count = Physics.OverlapSphereNonAlloc(
|
|
targetPoint, Mathf.Max(Radius, 0.01f), s_overlapBuffer, enemyLayerMask);
|
|
|
|
bool hitAny = false;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var enemyHealth = s_overlapBuffer[i].GetComponent<EnemyHealth>();
|
|
if (enemyHealth == null || enemyHealth.IsDead) continue;
|
|
|
|
enemyHealth.TakeDamage(Damage, DamageType.Fire, owner);
|
|
hitAny = true;
|
|
}
|
|
|
|
return hitAny;
|
|
}
|
|
|
|
public override void ClientSpawnVfx(Vector3 targetPoint)
|
|
{
|
|
// The meteor VFX plays its full fall-and-crash animation from here; it reaches the
|
|
// ground after ImpactDelay, which is when ClientPlayImpact and ServerCast fire.
|
|
if (impactVfxPrefab != null)
|
|
Destroy(Instantiate(impactVfxPrefab, targetPoint, Quaternion.identity), impactVfxLifetime);
|
|
}
|
|
|
|
public override void ClientPlayImpact(Vector3 targetPoint)
|
|
{
|
|
if (impactSound.clip != null)
|
|
AudioManager.Instance?.Play(impactSound.clip, AudioCategory.Combat,
|
|
impactSound.RandomPitch(), impactSound.volume);
|
|
}
|
|
}
|
|
}
|