diff --git a/Assets/Scripts/MatchManager.cs b/Assets/Scripts/MatchManager.cs index 7bc4e83..09a5730 100644 --- a/Assets/Scripts/MatchManager.cs +++ b/Assets/Scripts/MatchManager.cs @@ -29,8 +29,9 @@ using UnityEngine.SceneManagement; /// /// Flow: server start -> additive sync mode set -> Lobby loaded additively -> Player spawned for every /// connected peer (full mode set, including First Person). Late joiners spawn on connect into the -/// already-loaded scene. The host walks into the Start Match trigger -> : one -/// random connected client is rolled Hunter, the rest Hider; the Lobby is unloaded and Test Map loaded +/// already-loaded scene. The host walks into the Start Match trigger -> : a +/// configurable number of random connected clients are rolled Hunter (, +/// default 1), the rest Hider; the Lobby is unloaded and Test Map loaded /// (strictly serialized - an NGO constraint, only one scene op in flight); then every player object is /// despawned and respawned as its role's prefab variant. /// @@ -81,6 +82,12 @@ public class MatchManager : MonoBehaviour "free-cam rig is a separate scene object. Must be a registered network prefab.")] [SerializeField] private GameObject spectatorPrefab; + [Header("Roles")] + [Tooltip("How many randomly chosen players become Hunters at match start; everyone else is a Hider. " + + "0 = all Hiders; a value at or above the player count makes everyone a Hunter. Default 1.")] + [Min(0)] + [SerializeField] private int hunterCount = 1; + [Header("Spawn")] [Tooltip("World position lobby avatars are spawned at (raised a touch above the floor so gravity " + "settles them). Per-client ring fan-out is applied on top (NetworkPlayerSetup.PlaceOnSpawnRing).")] @@ -351,13 +358,26 @@ public class MatchManager : MonoBehaviour if (ids.Count == 0) return; Phase = MatchPhase.Transitioning; - ulong hunterId = ids[_roleRng.Next(ids.Count)]; - Debug.Log($"[MatchManager] Starting match. Hunter = client {hunterId}, {ids.Count} player(s).", this); - StartCoroutine(MatchSequence(hunterId)); + // Roll N distinct random Hunters (clamped to the player count); everyone else is a Hider. + // hunterCount 0 -> all Hiders, >= player count -> all Hunters. Partial Fisher-Yates: swap a + // random not-yet-picked id into each leading slot, then take that slot as a Hunter. + int wanted = Mathf.Clamp(hunterCount, 0, ids.Count); + var hunterIds = new HashSet(); + for (int i = 0; i < wanted; i++) + { + int j = i + _roleRng.Next(ids.Count - i); + (ids[i], ids[j]) = (ids[j], ids[i]); + hunterIds.Add(ids[i]); + } + + Debug.Log($"[MatchManager] Starting match. {hunterIds.Count} hunter(s), " + + $"{ids.Count - hunterIds.Count} hider(s), {ids.Count} player(s).", this); + + StartCoroutine(MatchSequence(hunterIds)); } - private IEnumerator MatchSequence(ulong hunterId) + private IEnumerator MatchSequence(HashSet hunterIds) { var sm = _net.SceneManager; @@ -371,7 +391,7 @@ public class MatchManager : MonoBehaviour SceneEventType.LoadEventCompleted, matchSceneName); // 3. Respawn every player as its rolled role's prefab variant. - RespawnAll(id => (id == hunterId) ? hunterPrefab : hiderPrefab, matchSpawnCenter); + RespawnAll(id => hunterIds.Contains(id) ? hunterPrefab : hiderPrefab, matchSpawnCenter); Phase = MatchPhase.InMatch; Debug.Log("[MatchManager] Match started.", this);