Adding Match State controller
This commit is contained in:
parent
c100db52e5
commit
abcefcd7f1
13 changed files with 445 additions and 99 deletions
153
Assets/_Project/Scripts/Gameplay/PlayerMatchState.cs
Normal file
153
Assets/_Project/Scripts/Gameplay/PlayerMatchState.cs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
// Assets/_Project/Scripts/Gameplay/PlayerMatchState.cs
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using TD.Core;
|
||||
|
||||
namespace TD.Gameplay
|
||||
{
|
||||
/// <summary>
|
||||
/// Per-player match state. One instance is spawned per connected client (via
|
||||
/// <see cref="NetworkManager.PlayerPrefab"/>) and owned by that client.
|
||||
///
|
||||
/// Mirrors the <see cref="PlayerGoldManager"/> pattern: static registry keyed by
|
||||
/// <c>OwnerClientId</c>, server-authoritative <c>NetworkVariable</c>s, and a
|
||||
/// <c>Local</c> convenience accessor.
|
||||
///
|
||||
/// The server assigns a <see cref="PlayerSlot"/> on spawn using the next free slot
|
||||
/// (Player1..Player9 in connect order). All five former STUB mappings
|
||||
/// (Builder, TowerPlacementManager, TowerPlacementController, CameraController,
|
||||
/// PlayerBuilderSpawner) delegate here instead.
|
||||
/// </summary>
|
||||
public class PlayerMatchState : NetworkBehaviour
|
||||
{
|
||||
// --- Static registry ---------------------------------------------
|
||||
|
||||
private static readonly Dictionary<ulong, PlayerMatchState> s_byClientId
|
||||
= new Dictionary<ulong, PlayerMatchState>();
|
||||
|
||||
// Tracks which slots are currently occupied so the server can assign the next free one.
|
||||
private static readonly HashSet<PlayerSlot> s_assignedSlots = new HashSet<PlayerSlot>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="PlayerMatchState"/> for the given client, or null if not spawned.
|
||||
/// Safe to call on server or client.
|
||||
/// </summary>
|
||||
public static PlayerMatchState GetForClient(ulong clientId)
|
||||
{
|
||||
s_byClientId.TryGetValue(clientId, out var state);
|
||||
return state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="PlayerSlot"/> for the given client, or <see cref="PlayerSlot.None"/>
|
||||
/// if the client has no spawned <see cref="PlayerMatchState"/>.
|
||||
/// Convenience wrapper over <see cref="GetForClient"/>.
|
||||
/// </summary>
|
||||
public static PlayerSlot SlotForClient(ulong clientId)
|
||||
=> GetForClient(clientId)?.Slot ?? PlayerSlot.None;
|
||||
|
||||
/// <summary>
|
||||
/// The local client's own state. Null on a dedicated server or before the
|
||||
/// local player has spawned.
|
||||
/// </summary>
|
||||
public static PlayerMatchState Local
|
||||
{
|
||||
get
|
||||
{
|
||||
var nm = NetworkManager.Singleton;
|
||||
if (nm == null || !nm.IsClient) return null;
|
||||
return GetForClient(nm.LocalClientId);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Networked state ---------------------------------------------
|
||||
|
||||
private readonly NetworkVariable<PlayerSlot> slot = new NetworkVariable<PlayerSlot>(
|
||||
value: PlayerSlot.None,
|
||||
readPerm: NetworkVariableReadPermission.Everyone,
|
||||
writePerm: NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
private readonly NetworkVariable<FixedString32Bytes> displayName = new NetworkVariable<FixedString32Bytes>(
|
||||
value: default,
|
||||
readPerm: NetworkVariableReadPermission.Everyone,
|
||||
writePerm: NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
// STUBBED — written by the race-pick system (Phase 1.8). None until a race is chosen.
|
||||
[Tooltip("STUBBED — not written until the race-pick system lands (Phase 1.8).")]
|
||||
private readonly NetworkVariable<RaceId> raceSelection = new NetworkVariable<RaceId>(
|
||||
value: RaceId.None,
|
||||
readPerm: NetworkVariableReadPermission.Everyone,
|
||||
writePerm: NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
/// <summary>This player's assigned slot in the match. Authoritative once spawned.</summary>
|
||||
public PlayerSlot Slot => slot.Value;
|
||||
|
||||
/// <summary>Display name. Stub until a lobby/name system provides it.</summary>
|
||||
public string DisplayName => displayName.Value.ToString();
|
||||
|
||||
/// <summary>STUBBED. Race chosen by this player. <see cref="RaceId.None"/> until Phase 1.8.</summary>
|
||||
public RaceId RaceSelection => raceSelection.Value;
|
||||
|
||||
/// <summary>STUBBED. Sets this player's race selection. Called by the race-pick system (Phase 1.8).</summary>
|
||||
public void SetRaceSelection(RaceId race)
|
||||
{
|
||||
if (!IsServer) { Debug.LogWarning("[PlayerMatchState] SetRaceSelection called on a client — ignored."); return; }
|
||||
raceSelection.Value = race;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-only. Fires on the server immediately after the slot is assigned in
|
||||
/// <see cref="OnNetworkSpawn"/>. Sibling components (e.g. PlayerBuilderSpawner)
|
||||
/// subscribe to defer work that requires a valid slot.
|
||||
/// </summary>
|
||||
public event System.Action<PlayerSlot> SlotReady;
|
||||
|
||||
// --- Lifecycle ---------------------------------------------------
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
s_byClientId[OwnerClientId] = this;
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
PlayerSlot assigned = AllocateNextSlot();
|
||||
slot.Value = assigned;
|
||||
displayName.Value = new FixedString32Bytes($"Player {(int)assigned}");
|
||||
s_assignedSlots.Add(assigned);
|
||||
|
||||
Debug.Log($"[PlayerMatchState] Assigned slot {assigned} to client {OwnerClientId}.");
|
||||
SlotReady?.Invoke(assigned);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this)
|
||||
s_byClientId.Remove(OwnerClientId);
|
||||
|
||||
if (IsServer)
|
||||
s_assignedSlots.Remove(slot.Value);
|
||||
}
|
||||
|
||||
// --- Slot allocation (server-only) -------------------------------
|
||||
|
||||
private static PlayerSlot AllocateNextSlot()
|
||||
{
|
||||
for (int i = 1; i <= 9; i++)
|
||||
{
|
||||
var candidate = (PlayerSlot)i;
|
||||
if (!s_assignedSlots.Contains(candidate))
|
||||
return candidate;
|
||||
}
|
||||
|
||||
Debug.LogError("[PlayerMatchState] No free PlayerSlot (already 9 players). " +
|
||||
"Returning None — this client will be unable to play.");
|
||||
return PlayerSlot.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue