Skip to content

Commit

Permalink
Perform handshake before spawning players
Browse files Browse the repository at this point in the history
  • Loading branch information
toberge committed Dec 9, 2024
1 parent 8ae96ad commit 459f263
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 77 deletions.
10 changes: 3 additions & 7 deletions Assets/Scripts/Control&Input/ItemSelectManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@
using Mirror;
using UnityEngine;

internal struct ClientReadyMessage : NetworkMessage
public class ItemSelectManager : NetworkBehaviour
{
}
private struct ClientReadyMessage : NetworkMessage { }

internal struct ClientNotReadyMessage : NetworkMessage
{
}
private struct ClientNotReadyMessage : NetworkMessage { }

public class ItemSelectManager : NetworkBehaviour
{
[SerializeField] private float graceTime = 1;

private List<ItemSelectMenu> itemSelectMenus;
Expand Down
200 changes: 130 additions & 70 deletions Assets/Scripts/Control&Input/RPRNetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using UnityEngine.SceneManagement;

// TODO consider splitting this into match-specific state and general player metadata
// TODO also consider sticking some of the metadata onto the playermanager/playeridentity component for ease of access...
public struct PlayerDetails
{
public uint id;
Expand Down Expand Up @@ -41,95 +42,102 @@ public PlayerConnectedMessage(int inputID, ulong steamID = 0)
public ulong steamID;
}

public struct PlayerLeftMessage : NetworkMessage
{
public PlayerLeftMessage(uint id)
{
this.id = id;
}

public uint id;
public enum PlayerType
{
Local,
AI,
Remote
}

public struct RulesetMessage : NetworkMessage
public class RPRNetworkManager : NetworkManager
{
public RulesetMessage(Ruleset ruleset)
#region Messages
private struct PlayerLeftMessage : NetworkMessage
{
this.ruleset = ruleset.ToNetworkRuleset();
public PlayerLeftMessage(uint id)
{
this.id = id;
}

public uint id;
}
public NetworkRuleset ruleset;
}

public struct InitialPlayerDetailsMessage : NetworkMessage
{
public InitialPlayerDetailsMessage(PlayerDetails details)
private struct RulesetMessage : NetworkMessage
{
this.details = details;
public RulesetMessage(Ruleset ruleset)
{
this.ruleset = ruleset.ToNetworkRuleset();
}
public NetworkRuleset ruleset;
}
public PlayerDetails details;
}

public struct UpdatedPlayerDetailsMessage : NetworkMessage
{
public UpdatedPlayerDetailsMessage(PlayerDetails details)
private struct InitialPlayerDetailsMessage : NetworkMessage
{
this.details = details;
public InitialPlayerDetailsMessage(PlayerDetails details)
{
this.details = details;
}
public PlayerDetails details;
}
public PlayerDetails details;
}

public struct UpdateLoadoutMessage : NetworkMessage
{
public UpdateLoadoutMessage(uint id, string body, string barrel, string extension)
private struct UpdatedPlayerDetailsMessage : NetworkMessage
{
this.id = id;
this.body = body;
this.barrel = barrel;
this.extension = extension;
public UpdatedPlayerDetailsMessage(PlayerDetails details)
{
this.details = details;
}
public PlayerDetails details;
}
public uint id;
public string body;
public string barrel;
public string extension;
}

public struct StartMatchMessage : NetworkMessage { }

public struct SpawnPlayerMessage : NetworkMessage
{
public SpawnPlayerMessage(uint id, PlayerType type)
private struct UpdateLoadoutMessage : NetworkMessage
{
this.id = id;
this.type = type;
public UpdateLoadoutMessage(uint id, string body, string barrel, string extension)
{
this.id = id;
this.body = body;
this.barrel = barrel;
this.extension = extension;
}
public uint id;
public string body;
public string barrel;
public string extension;
}

public uint id;
public PlayerType type;
}
private struct StartMatchMessage : NetworkMessage { }

public struct InitializePlayerMessage : NetworkMessage
{
public InitializePlayerMessage(uint id, Vector3 position, Quaternion rotation)
private struct ClientReadyMessage : NetworkMessage { }

private struct AllClientsReadyMessage : NetworkMessage { }

private struct SpawnPlayerMessage : NetworkMessage
{
this.id = id;
this.position = position;
this.rotation = rotation;
public SpawnPlayerMessage(uint id)
{
this.id = id;
}

public uint id;
}

public uint id;
public Vector3 position;
public Quaternion rotation;
}
private struct InitializePlayerMessage : NetworkMessage
{
public InitializePlayerMessage(uint id, Vector3 position, Quaternion rotation)
{
this.id = id;
this.position = position;
this.rotation = rotation;
}

public enum PlayerType
{
Local,
AI,
Remote
}
public uint id;
public Vector3 position;
public Quaternion rotation;
}
#endregion;

#region State

public class RPRNetworkManager : NetworkManager
{
private const int FPSPlayerPrefabIndex = 0;
private const int BiddingPlayerPrefabIndexOffset = 1;
private const int AIFPSPlayerPrefabIndex = 2;
Expand All @@ -146,6 +154,8 @@ public class RPRNetworkManager : NetworkManager
private static bool isInMatch;
public static bool IsInMatch => isInMatch;

private static bool allClientsAreReady;

private static Dictionary<uint, PlayerDetails> players = new();
public static int NumPlayers => players.Count;
public static int NumAvailableSlots => Mathf.Max(0, MaxPlayers - players.Count);
Expand All @@ -163,6 +173,7 @@ public class RPRNetworkManager : NetworkManager
/// </summary>
private static List<NetworkConnectionToClient> connections = new();
private static Dictionary<int, List<uint>> playersForConnection = new();
private static Dictionary<int, bool> readinessForConnection = new();
private static List<uint> connectedPlayers = new();
public static ReadOnlyCollection<NetworkConnectionToClient> Connections;

Expand All @@ -177,9 +188,14 @@ public class RPRNetworkManager : NetworkManager
public GamestateEvent OnMatchStart;
public GamestateEvent OnMatchEnd;

#endregion

#region Initializing

private void ResetState()
{
isInMatch = false;
allClientsAreReady = false;
playerIndex = 0;
players = new();
playerInstances = new();
Expand All @@ -190,22 +206,24 @@ private void ResetState()
connections = new();
connectedPlayers = new();
playersForConnection = new();
readinessForConnection = new();
Connections = new(connections);
}

public override void OnStartServer()
{
NetworkServer.RegisterHandler<PlayerConnectedMessage>(OnSpawnPlayerInput);
NetworkServer.RegisterHandler<UpdateLoadoutMessage>(OnReceiveUpdateLoadout);
NetworkServer.RegisterHandler<UpdateLoadoutMessage>(OnReceiveUpdatedLoadout);
NetworkServer.RegisterHandler<ClientReadyMessage>(OnClientReady);

ResetState();
}

#region Player joining

public override void OnClientConnect()
{
base.OnClientConnect();
NetworkClient.RegisterHandler<AllClientsReadyMessage>(OnAllClientsReady);
NetworkClient.RegisterHandler<StartMatchMessage>(OnStartMatch);
NetworkClient.RegisterHandler<PlayerLeftMessage>(OnPlayerLeft);
NetworkClient.RegisterHandler<InitialPlayerDetailsMessage>(OnReceivePlayerDetails);
Expand All @@ -220,6 +238,10 @@ public override void OnClientConnect()
ResetState();
}

#endregion

#region Disconnecting

public override void OnServerDisconnect(NetworkConnectionToClient connection)
{
if (playersForConnection == null || !playersForConnection.ContainsKey(connection.connectionId))
Expand Down Expand Up @@ -291,6 +313,10 @@ private void OnPlayerLeft(PlayerLeftMessage message)
OnPlayerRemoved?.Invoke(playerDetails);
}

#endregion

#region Network startup

public void JoinLobby(string address = "127.0.0.1")
{
if (NetworkServer.active)
Expand Down Expand Up @@ -346,6 +372,10 @@ private static IEnumerator WaitAndSwitchToTrainingMode()
MusicTrackManager.Singleton.SwitchTo(MusicType.Tutorial);
}

#endregion

#region Match start

// TODO custom method for leaving training mode :)
// TODO handle leaving of match/lobby better

Expand All @@ -369,6 +399,10 @@ private void OnStartMatch(StartMatchMessage message)
LoadingScreen.Singleton.Show();
}

#endregion

#region Joining

private void RefuseConnection(NetworkConnectionToClient connection)
{
// Don't refuse existing connections
Expand Down Expand Up @@ -631,7 +665,7 @@ public void UpdateLoadout()
}
}

private void OnReceiveUpdateLoadout(NetworkConnectionToClient connection, UpdateLoadoutMessage message)
private void OnReceiveUpdatedLoadout(NetworkConnectionToClient connection, UpdateLoadoutMessage message)
{
if (!players.TryGetValue(message.id, out var playerDetails))
return;
Expand All @@ -646,6 +680,7 @@ private void OnReceiveUpdateLoadout(NetworkConnectionToClient connection, Update

public override void OnClientChangeScene(string newSceneName, SceneOperation sceneOperation, bool customHandling)
{
allClientsAreReady = false;
var originalSceneName = SceneManager.GetActiveScene().name;
switch (newSceneName)
{
Expand All @@ -672,15 +707,40 @@ public override void OnClientChangeScene(string newSceneName, SceneOperation sce
StartCoroutine(SendSpawnRequestsAfterSceneLoad(originalSceneName));
}

#endregion

#region Start of round handshake

private void OnClientReady(NetworkConnectionToClient connection, ClientReadyMessage _)
{
readinessForConnection[connection.connectionId] = true;
if (Connections.All(c => readinessForConnection.TryGetValue(c.connectionId, out var isReady) && isReady))
NetworkServer.SendToAll(new AllClientsReadyMessage());
}

private void OnAllClientsReady(AllClientsReadyMessage _)
{
allClientsAreReady = true;
}

private IEnumerator SendSpawnRequestsAfterSceneLoad(string originalSceneName)
{
readinessForConnection = new();
allClientsAreReady = false;

while (SceneManager.GetActiveScene().name == originalSceneName)
yield return null;

NetworkClient.Send(new ClientReadyMessage());

// Wait for ready msg from clients!
while (!allClientsAreReady)
yield return null;

// Spawn players for our inputs (and bots)
foreach (var id in localPlayerIds)
{
NetworkClient.Send(new SpawnPlayerMessage(id, PlayerType.Local));
NetworkClient.Send(new SpawnPlayerMessage(id));
}

if (!NetworkServer.active)
Expand All @@ -691,12 +751,12 @@ private IEnumerator SendSpawnRequestsAfterSceneLoad(string originalSceneName)
// (perhaps you could call the spawn methods directly?)
foreach (var p in players.Values.Where(p => p.type == PlayerType.AI))
{
NetworkClient.Send(new SpawnPlayerMessage(p.id, PlayerType.AI));
NetworkClient.Send(new SpawnPlayerMessage(p.id));
}

foreach (var p in players.Values.Where(p => p.type is PlayerType.Remote && !connectedPlayers.Contains(p.id)))
{
NetworkClient.Send(new SpawnPlayerMessage(p.id, PlayerType.Local));
NetworkClient.Send(new SpawnPlayerMessage(p.id));
}
}

Expand Down

0 comments on commit 459f263

Please sign in to comment.