draft options working; spells won't cast though
This commit is contained in:
parent
2429efbf6a
commit
7cf15dcaa6
37 changed files with 1340 additions and 0 deletions
|
|
@ -0,0 +1,91 @@
|
|||
// Assets/_Project/Scripts/Gameplay/BuilderSpells/BuilderSpellDefinition.cs
|
||||
using UnityEngine;
|
||||
using TD.Core;
|
||||
|
||||
namespace TD.Gameplay.BuilderSpells
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 (<see
|
||||
/// cref="TD.Gameplay.Draft.BuilderSpellDraftOption"/>) and tracked per-player by <see
|
||||
/// cref="PlayerSpellLoadout"/>, which owns the hotkey slot and cooldown state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Contrast with <see cref="BuilderEffects.BuilderEffectDefinition"/>.</b> Effects
|
||||
/// are passive and queried by consumers; spells are active and resolve themselves.
|
||||
/// <see cref="ServerCast"/> is that resolution — it IS the "apply the effect" method,
|
||||
/// replacing the effect system's <c>IsValidFor</c>-only gate pattern (grant-time validity
|
||||
/// still lives on <see cref="TD.Gameplay.Draft.BuilderSpellDraftOption.IsValidFor"/>).</para>
|
||||
///
|
||||
/// <para><b>One asset per kind.</b> <see cref="Kind"/> is fixed per subclass, same as
|
||||
/// <see cref="BuilderEffects.BuilderEffectDefinition"/>. <see cref="BuilderSpellPool"/> uses
|
||||
/// it to build a fixed-size, enum-indexed lookup table.</para>
|
||||
///
|
||||
/// <para><b>Server-only resolution, client-only visual.</b> <see cref="ServerCast"/> runs on
|
||||
/// the server and applies damage/status via the same <c>Physics.OverlapSphereNonAlloc</c> +
|
||||
/// <c>EnemyHealth</c>/<c>EnemyStatus</c> pattern <c>TowerCombat</c> already uses.
|
||||
/// <see cref="ClientPlayVfx"/> runs on every peer (including the server) after a successful
|
||||
/// cast, purely for presentation.</para>
|
||||
/// </remarks>
|
||||
public abstract class BuilderSpellDefinition : ScriptableObject
|
||||
{
|
||||
/// <summary>Which builder spell this asset's data belongs to.</summary>
|
||||
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];
|
||||
|
||||
/// <summary>
|
||||
/// Server-only: resolve this spell's effect at <paramref name="targetPoint"/> for the
|
||||
/// casting player. Returns false if the cast could not be applied (e.g. hit nothing) —
|
||||
/// <see cref="PlayerSpellLoadout"/> treats a false return as a no-op and does not start
|
||||
/// the cooldown.
|
||||
/// </summary>
|
||||
public abstract bool ServerCast(ulong clientId, Vector3 targetPoint);
|
||||
|
||||
/// <summary>
|
||||
/// Runs on every peer (via <see cref="PlayerSpellLoadout"/>'s ClientRpc) after a
|
||||
/// successful <see cref="ServerCast"/>. Default no-op; override to spawn an impact VFX
|
||||
/// prefab and self-destroy it, the same idiom used elsewhere for one-off visuals.
|
||||
/// </summary>
|
||||
public virtual void ClientPlayVfx(Vector3 targetPoint) { }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue