Adding 9 Player level
This commit is contained in:
parent
fdada6f132
commit
a7be12fa9b
30 changed files with 45984 additions and 300 deletions
|
|
@ -2,6 +2,7 @@
|
|||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using TD.Core;
|
||||
using TD.Levels;
|
||||
using TD.Net;
|
||||
|
||||
namespace TD.Gameplay
|
||||
|
|
@ -51,6 +52,40 @@ namespace TD.Gameplay
|
|||
|
||||
public static LobbyService Instance { get; private set; }
|
||||
|
||||
// ----- Networked lobby state --------------------------------------
|
||||
|
||||
// Index into MapRegistry.Maps of the currently selected map. Server-write,
|
||||
// everyone-read. Default 0 (the first registered map = MapRegistry.Default).
|
||||
// UI subscribes to OnValueChanged to refresh the map browser highlight.
|
||||
// A stale index (map removed between sessions) is tolerated by MapRegistry.Get
|
||||
// returning null; RequestStartMatchRpc validates before loading.
|
||||
private readonly NetworkVariable<int> selectedMapIndex =
|
||||
new NetworkVariable<int>(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently selected map within <see cref="MapRegistry.Maps"/>.
|
||||
/// All clients read the same value; only the host can change it via
|
||||
/// <see cref="RequestSelectMapRpc"/>.
|
||||
/// </summary>
|
||||
public int SelectedMapIndex => selectedMapIndex.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Exposed for UI subscription to <c>OnValueChanged</c>. Treat as read-only —
|
||||
/// mutations must go through the server via <see cref="RequestSelectMapRpc"/>
|
||||
/// so the host-only gate is enforced.
|
||||
/// </summary>
|
||||
public NetworkVariable<int> SelectedMapIndexVar => selectedMapIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Convenience: resolves the currently selected <see cref="LevelData"/> via
|
||||
/// <see cref="MapRegistry"/>. Returns null if the registry is missing (e.g. the
|
||||
/// editor was started directly in the Lobby scene) or the index is stale.
|
||||
/// </summary>
|
||||
public LevelData SelectedMap =>
|
||||
MapRegistry.Instance != null
|
||||
? MapRegistry.Instance.Get(selectedMapIndex.Value)
|
||||
: null;
|
||||
|
||||
// ----- Lifecycle --------------------------------------------------
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
|
|
@ -67,7 +102,19 @@ namespace TD.Gameplay
|
|||
// are preserved from the previous lobby visit, but ready state
|
||||
// resets so each match requires explicit re-readying.
|
||||
if (IsServer)
|
||||
{
|
||||
ResetAllReady();
|
||||
|
||||
// If the current selection is invalid for any reason (registry missing,
|
||||
// index stale from a previous session), snap to the default. Index 0
|
||||
// is already the default-default; this is a no-op except after the
|
||||
// registry's contents change between sessions.
|
||||
var registry = MapRegistry.Instance;
|
||||
if (registry != null && registry.Get(selectedMapIndex.Value) == null && registry.Count > 0)
|
||||
{
|
||||
selectedMapIndex.Value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
|
|
@ -79,8 +126,9 @@ namespace TD.Gameplay
|
|||
|
||||
/// <summary>
|
||||
/// Host-only request to begin the match. Validates that every connected
|
||||
/// player has picked a race and is ready, then transitions every peer
|
||||
/// to the Match scene via NGO scene management.
|
||||
/// player has picked a race and is ready AND that the selected map can
|
||||
/// accommodate the current lobby's player count, then transitions every
|
||||
/// peer to the selected map's scene via NGO scene management.
|
||||
/// </summary>
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
public void RequestStartMatchRpc(RpcParams rpcParams = default)
|
||||
|
|
@ -100,7 +148,81 @@ namespace TD.Gameplay
|
|||
return;
|
||||
}
|
||||
|
||||
NetworkBootstrap.LoadSceneAsHost(SceneNames.Match);
|
||||
// Re-validate the selected map server-side. The UI greys out unselectable
|
||||
// maps based on lobby size, but a late join could push us above the map's
|
||||
// PlayerCount between the click and the RPC arriving. Defensive check.
|
||||
var registry = MapRegistry.Instance;
|
||||
if (registry == null)
|
||||
{
|
||||
Debug.LogError("[LobbyService] Cannot start match: MapRegistry.Instance is null. " +
|
||||
"Make sure MainMenu was loaded before the lobby (the registry " +
|
||||
"DontDestroyOnLoads from there).");
|
||||
return;
|
||||
}
|
||||
|
||||
var selected = registry.Get(selectedMapIndex.Value);
|
||||
if (selected == null)
|
||||
{
|
||||
Debug.LogError($"[LobbyService] Cannot start match: selected map index " +
|
||||
$"{selectedMapIndex.Value} is not registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
int playerCount = CountConnectedPlayers();
|
||||
if (!MapRegistry.IsSelectableFor(selected, playerCount))
|
||||
{
|
||||
Debug.Log($"[LobbyService] Cannot start match: map '{selected.MapName}' supports " +
|
||||
$"up to {selected.PlayerCount} players but the lobby has {playerCount}.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(selected.SceneName))
|
||||
{
|
||||
Debug.LogError($"[LobbyService] Cannot start match: '{selected.MapName}' has " +
|
||||
$"empty SceneName (ScenePath='{selected.ScenePath}'). Re-bake the level.");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[LobbyService] Starting match on '{selected.MapName}' " +
|
||||
$"(index={selectedMapIndex.Value}, scene='{selected.SceneName}', " +
|
||||
$"scenePath='{selected.ScenePath}', players={playerCount}).");
|
||||
NetworkBootstrap.LoadSceneAsHost(selected.SceneName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Host-only request to change the selected map. Validates that the requested index
|
||||
/// resolves to a real map in <see cref="MapRegistry"/>; if so, writes
|
||||
/// <see cref="SelectedMapIndex"/> which replicates to every client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Selectability for the current lobby size is NOT enforced here — players are
|
||||
/// allowed to highlight an oversized map (e.g. while waiting for more friends to
|
||||
/// join); the actual Start Match call enforces the rule. This keeps the host's
|
||||
/// intent visible to everyone without preventing them from "claiming" a future map.
|
||||
/// </remarks>
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
public void RequestSelectMapRpc(int mapIndex, RpcParams rpcParams = default)
|
||||
{
|
||||
if (rpcParams.Receive.SenderClientId != NetworkManager.Singleton.LocalClientId)
|
||||
{
|
||||
Debug.LogWarning("[LobbyService] Non-host client attempted to change the map. Ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
var registry = MapRegistry.Instance;
|
||||
if (registry == null)
|
||||
{
|
||||
Debug.LogError("[LobbyService] Cannot change map: MapRegistry.Instance is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (registry.Get(mapIndex) == null)
|
||||
{
|
||||
Debug.LogWarning($"[LobbyService] Rejected map index {mapIndex} — not registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
selectedMapIndex.Value = mapIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -154,6 +276,18 @@ namespace TD.Gameplay
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current number of connected players (every <see cref="PlayerMatchState"/> in the
|
||||
/// static registry). Used by map selectability checks both in the UI and the server-side
|
||||
/// Start Match validator.
|
||||
/// </summary>
|
||||
public static int CountConnectedPlayers()
|
||||
{
|
||||
int n = 0;
|
||||
foreach (var _ in PlayerMatchState.AllPlayers) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
// ----- Server helpers --------------------------------------------
|
||||
|
||||
private static void ResetAllReady()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue