Updates to spells, HUD, Gold, and Camera Controls. New sound effects!
This commit is contained in:
parent
689fe7be88
commit
a33d951abd
27 changed files with 578 additions and 68 deletions
|
|
@ -1,4 +1,5 @@
|
|||
// Assets/_Project/Scripts/Gameplay/PlayerSpellLoadout.cs
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
|
@ -20,9 +21,12 @@ namespace TD.Gameplay
|
|||
/// slot index and an aimed world point. The server re-validates everything (slot exists,
|
||||
/// off cooldown, <see cref="MatchPhase.Playing"/>) since client-side checks in
|
||||
/// <see cref="BuilderSpellCastController"/> are UX-only, resolves the
|
||||
/// <see cref="BuilderSpellDefinition"/> via <see cref="BuilderSpellPool"/>, and calls
|
||||
/// <see cref="BuilderSpellDefinition.ServerCast"/>. A successful cast starts the cooldown
|
||||
/// and fires <see cref="PlayVfxClientRpc"/> so every peer plays the same visual.</para>
|
||||
/// <see cref="BuilderSpellDefinition"/> via <see cref="BuilderSpellPool"/>. An <b>instant</b>
|
||||
/// spell (<see cref="BuilderSpellDefinition.ImpactDelay"/> == 0) resolves on cast: a hit starts
|
||||
/// the cooldown and fires <see cref="PlayVfxClientRpc"/>; a miss is a no-op. A <b>delayed</b>
|
||||
/// spell (a projectile, e.g. the Fireball meteor) commits on the throw — cooldown + the falling
|
||||
/// visual fire immediately — while its damage and impact sound are held until the projectile
|
||||
/// lands <see cref="BuilderSpellDefinition.ImpactDelay"/> seconds later.</para>
|
||||
/// </remarks>
|
||||
public class PlayerSpellLoadout : NetworkBehaviour
|
||||
{
|
||||
|
|
@ -142,7 +146,7 @@ namespace TD.Gameplay
|
|||
/// <paramref name="slot"/> at <paramref name="targetPoint"/>. All validation happens
|
||||
/// here on the server; the client only uses this to trigger an attempt.
|
||||
/// </summary>
|
||||
[Rpc(SendTo.Server, RequireOwnership = true)]
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Owner)]
|
||||
public void RequestCastSpellRpc(int slot, Vector3 targetPoint)
|
||||
{
|
||||
if (MatchState.Instance == null || MatchState.Instance.Phase != MatchPhase.Playing)
|
||||
|
|
@ -160,18 +164,69 @@ namespace TD.Gameplay
|
|||
return;
|
||||
}
|
||||
|
||||
if (!definition.ServerCast(OwnerClientId, targetPoint)) return;
|
||||
if (definition.ImpactDelay <= 0f)
|
||||
{
|
||||
// Instant spell (e.g. Slow Area): resolve on cast. A miss (hit nothing) is a no-op
|
||||
// and does NOT start the cooldown — unchanged behavior.
|
||||
if (!definition.ServerCast(OwnerClientId, targetPoint)) return;
|
||||
|
||||
StartCooldown(slot, definition);
|
||||
PlayVfxClientRpc(slotValue.Kind, targetPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delayed spell (e.g. the Fireball meteor): the projectile has to fall before it
|
||||
// lands, so the cast is COMMITTED on the throw — cooldown starts and the falling
|
||||
// visual spawns now — while damage and the impact sound wait until it hits the
|
||||
// ground (ImpactDelay later). Enemies are re-scanned at impact, so movement during
|
||||
// the fall resolves correctly.
|
||||
StartCooldown(slot, definition);
|
||||
PlayVfxClientRpc(slotValue.Kind, targetPoint);
|
||||
StartCoroutine(ServerResolveImpactAfterDelay(
|
||||
slotValue.Kind, OwnerClientId, targetPoint, definition.ImpactDelay));
|
||||
}
|
||||
}
|
||||
|
||||
// Server-only: stamp the slot's cooldown from the definition and replicate it.
|
||||
private void StartCooldown(int slot, BuilderSpellDefinition definition)
|
||||
{
|
||||
var slotValue = spells[slot];
|
||||
slotValue.CooldownEndServerTime = NetworkManager.ServerTime.Time + definition.Cooldown;
|
||||
spells[slot] = slotValue;
|
||||
}
|
||||
|
||||
PlayVfxClientRpc(slotValue.Kind, targetPoint);
|
||||
// Server-only: apply a delayed spell's effect once its projectile has landed. Re-resolves
|
||||
// targets at impact — the enemy set may have shifted during the fall.
|
||||
private IEnumerator ServerResolveImpactAfterDelay(
|
||||
BuilderSpellKind kind, ulong clientId, Vector3 targetPoint, float delay)
|
||||
{
|
||||
yield return new WaitForSeconds(delay);
|
||||
if (!IsServer) yield break;
|
||||
BuilderSpellPool.Instance?.Get(kind)?.ServerCast(clientId, targetPoint);
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private void PlayVfxClientRpc(BuilderSpellKind kind, Vector3 targetPoint)
|
||||
{
|
||||
BuilderSpellPool.Instance?.Get(kind)?.ClientPlayVfx(targetPoint);
|
||||
var def = BuilderSpellPool.Instance?.Get(kind);
|
||||
if (def == null) return;
|
||||
|
||||
def.ClientSpawnVfx(targetPoint); // spawn the (possibly falling) visual now
|
||||
|
||||
if (def.ImpactDelay <= 0f)
|
||||
def.ClientPlayImpact(targetPoint);
|
||||
else
|
||||
StartCoroutine(ClientPlayImpactAfterDelay(kind, targetPoint, def.ImpactDelay));
|
||||
}
|
||||
|
||||
// Client-side: play the impact (sound + any contact visual) when the projectile lands.
|
||||
// Timed locally off the same ImpactDelay so it stays in lockstep with this peer's own
|
||||
// falling visual — no second RPC, no double latency.
|
||||
private IEnumerator ClientPlayImpactAfterDelay(
|
||||
BuilderSpellKind kind, Vector3 targetPoint, float delay)
|
||||
{
|
||||
yield return new WaitForSeconds(delay);
|
||||
BuilderSpellPool.Instance?.Get(kind)?.ClientPlayImpact(targetPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue