Make the number of hunters configurable in MatchManager

StartMatch rolled exactly one random Hunter. Add a serialized hunterCount
field (default 1, [Min(0)], inspector-tunable) and roll N distinct random
Hunters via a partial Fisher-Yates on the client-id list, clamped to the
player count: 0 = all Hiders, >= player count = all Hunters. The match
sequence now carries a HashSet of hunter ids instead of a single id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-28 21:39:30 +02:00
1 parent 346a0c6e8c
commit 0834419a56
1 file changed
+27 -7
+27 -7
View File
@@ -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 -> <see cref="StartMatch"/>: 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 -> <see cref="StartMatch"/>: a
/// configurable number of random connected clients are rolled Hunter (<see cref="hunterCount"/>,
/// 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<ulong>();
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<ulong> 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);