|
@@ -0,0 +1,636 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
+using System.Net;
|
|
|
+using System.Net.Sockets;
|
|
|
+using System.Threading;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using Microsoft.Xna.Framework.GamerServices;
|
|
|
+
|
|
|
+namespace Microsoft.Xna.Framework.Net
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// Represents a network session for multiplayer gaming.
|
|
|
+ /// </summary>
|
|
|
+ public class NetworkSession : IDisposable
|
|
|
+ {
|
|
|
+ private readonly List<NetworkGamer> gamers;
|
|
|
+ private readonly GamerCollection gamerCollection;
|
|
|
+ private readonly NetworkSessionType sessionType;
|
|
|
+ private readonly int maxGamers;
|
|
|
+ private readonly int privateGamerSlots;
|
|
|
+ private readonly UdpClient udpClient;
|
|
|
+ private readonly Dictionary<string, IPEndPoint> gamerEndpoints;
|
|
|
+ private readonly object lockObject = new object();
|
|
|
+
|
|
|
+ private NetworkSessionState sessionState;
|
|
|
+ private bool disposed;
|
|
|
+ private bool isHost;
|
|
|
+ private string sessionId;
|
|
|
+ private Task receiveTask;
|
|
|
+ private CancellationTokenSource cancellationTokenSource;
|
|
|
+
|
|
|
+ // Events
|
|
|
+ public event EventHandler<GameStartedEventArgs> GameStarted;
|
|
|
+ public event EventHandler<GameEndedEventArgs> GameEnded;
|
|
|
+ public event EventHandler<GamerJoinedEventArgs> GamerJoined;
|
|
|
+ public event EventHandler<GamerLeftEventArgs> GamerLeft;
|
|
|
+ public event EventHandler<NetworkSessionEndedEventArgs> SessionEnded;
|
|
|
+
|
|
|
+ // Static event for invite acceptance
|
|
|
+ public static event EventHandler<InviteAcceptedEventArgs> InviteAccepted;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new NetworkSession.
|
|
|
+ /// </summary>
|
|
|
+ private NetworkSession(NetworkSessionType sessionType, int maxGamers, int privateGamerSlots, bool isHost)
|
|
|
+ {
|
|
|
+ this.sessionType = sessionType;
|
|
|
+ this.maxGamers = maxGamers;
|
|
|
+ this.privateGamerSlots = privateGamerSlots;
|
|
|
+ this.isHost = isHost;
|
|
|
+ this.sessionId = Guid.NewGuid().ToString();
|
|
|
+ this.sessionState = NetworkSessionState.Creating;
|
|
|
+
|
|
|
+ gamers = new List<NetworkGamer>();
|
|
|
+ gamerCollection = new GamerCollection(gamers);
|
|
|
+ gamerEndpoints = new Dictionary<string, IPEndPoint>();
|
|
|
+
|
|
|
+ // Initialize UDP client for networking
|
|
|
+ udpClient = new UdpClient();
|
|
|
+ cancellationTokenSource = new CancellationTokenSource();
|
|
|
+
|
|
|
+ // Add local gamer
|
|
|
+ var localGamer = new LocalNetworkGamer(this, Guid.NewGuid().ToString(), isHost, SignedInGamer.Current?.Gamertag ?? "Player");
|
|
|
+ NetworkGamer.LocalGamer = localGamer;
|
|
|
+ AddGamer(localGamer);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets all gamers in the session.
|
|
|
+ /// </summary>
|
|
|
+ public GamerCollection AllGamers => gamerCollection;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets local gamers in the session.
|
|
|
+ /// </summary>
|
|
|
+ public LocalGamerCollection LocalGamers
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ var localGamers = gamers.Where(g => g.IsLocal).OfType<LocalNetworkGamer>().ToList();
|
|
|
+ return new LocalGamerCollection(localGamers);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets remote gamers in the session.
|
|
|
+ /// </summary>
|
|
|
+ public GamerCollection RemoteGamers
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ var remoteGamers = gamers.Where(g => !g.IsLocal).ToList();
|
|
|
+ return new GamerCollection(remoteGamers);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the host gamer.
|
|
|
+ /// </summary>
|
|
|
+ public NetworkGamer Host => AllGamers.Host;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets whether this machine is the host.
|
|
|
+ /// </summary>
|
|
|
+ public bool IsHost => isHost;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets whether everyone is ready to start the game.
|
|
|
+ /// </summary>
|
|
|
+ public bool IsEveryoneReady => AllGamers.All(g => g.IsReady);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the maximum number of gamers.
|
|
|
+ /// </summary>
|
|
|
+ public int MaxGamers => maxGamers;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the number of private gamer slots.
|
|
|
+ /// </summary>
|
|
|
+ public int PrivateGamerSlots => privateGamerSlots;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the session type.
|
|
|
+ /// </summary>
|
|
|
+ public NetworkSessionType SessionType => sessionType;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the current session state.
|
|
|
+ /// </summary>
|
|
|
+ public NetworkSessionState SessionState => sessionState;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the bytes per second being sent.
|
|
|
+ /// </summary>
|
|
|
+ public int BytesPerSecondSent => 0; // Mock implementation
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the bytes per second being received.
|
|
|
+ /// </summary>
|
|
|
+ public int BytesPerSecondReceived => 0; // Mock implementation
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets whether the session allows host migration.
|
|
|
+ /// </summary>
|
|
|
+ public bool AllowHostMigration { get; set; } = true;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets whether the session allows gamers to join during gameplay.
|
|
|
+ /// </summary>
|
|
|
+ public bool AllowJoinInProgress { get; set; } = true;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets custom session properties.
|
|
|
+ /// </summary>
|
|
|
+ public IDictionary<string, object> SessionProperties { get; set; } = new Dictionary<string, object>();
|
|
|
+
|
|
|
+ // Simulation properties for testing network conditions
|
|
|
+ private TimeSpan simulatedLatency = TimeSpan.Zero;
|
|
|
+ private float simulatedPacketLoss = 0.0f;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the simulated network latency for testing purposes.
|
|
|
+ /// </summary>
|
|
|
+ public TimeSpan SimulatedLatency
|
|
|
+ {
|
|
|
+ get => simulatedLatency;
|
|
|
+ set => simulatedLatency = value;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the simulated packet loss percentage for testing purposes.
|
|
|
+ /// </summary>
|
|
|
+ public float SimulatedPacketLoss
|
|
|
+ {
|
|
|
+ get => simulatedPacketLoss;
|
|
|
+ set => simulatedPacketLoss = Math.Max(0.0f, Math.Min(1.0f, value));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Begins creating a new network session.
|
|
|
+ /// </summary>
|
|
|
+ public static IAsyncResult BeginCreate(
|
|
|
+ NetworkSessionType sessionType,
|
|
|
+ IEnumerable<SignedInGamer> localGamers,
|
|
|
+ int maxGamers,
|
|
|
+ int privateGamerSlots,
|
|
|
+ NetworkSessionProperties sessionProperties,
|
|
|
+ AsyncCallback callback,
|
|
|
+ object asyncState
|
|
|
+ )
|
|
|
+ {
|
|
|
+ if (maxGamers < 1 || maxGamers > 4)
|
|
|
+ {
|
|
|
+ throw new ArgumentOutOfRangeException(nameof(maxGamers));
|
|
|
+ }
|
|
|
+ if (privateGamerSlots < 0 || privateGamerSlots > maxGamers)
|
|
|
+ {
|
|
|
+ throw new ArgumentOutOfRangeException(nameof(privateGamerSlots));
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new NotImplementedException("Async creation with callback is not implemented yet.");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IAsyncResult BeginCreate(
|
|
|
+ NetworkSessionType sessionType,
|
|
|
+ int maxLocalGamers,
|
|
|
+ int maxGamers,
|
|
|
+ AsyncCallback callback = null,
|
|
|
+ object asyncState = null
|
|
|
+ )
|
|
|
+ {
|
|
|
+ if (maxLocalGamers < 1 || maxLocalGamers > 4)
|
|
|
+ {
|
|
|
+ throw new ArgumentOutOfRangeException(nameof(maxLocalGamers));
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new NotImplementedException("Async creation with callback is not implemented yet.");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IAsyncResult BeginCreate(
|
|
|
+ NetworkSessionType sessionType,
|
|
|
+ int maxLocalGamers,
|
|
|
+ int maxGamers,
|
|
|
+ int privateGamerSlots,
|
|
|
+ NetworkSessionProperties sessionProperties,
|
|
|
+ AsyncCallback callback = null,
|
|
|
+ object asyncState = null
|
|
|
+ )
|
|
|
+ {
|
|
|
+ if (maxLocalGamers < 1 || maxLocalGamers > 4)
|
|
|
+ {
|
|
|
+ throw new ArgumentOutOfRangeException(nameof(maxLocalGamers));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (maxLocalGamers < 1 || maxLocalGamers > 4)
|
|
|
+ {
|
|
|
+ throw new ArgumentOutOfRangeException(nameof(maxLocalGamers));
|
|
|
+ }
|
|
|
+ if (privateGamerSlots < 0 || privateGamerSlots > maxGamers)
|
|
|
+ {
|
|
|
+ throw new ArgumentOutOfRangeException(nameof(privateGamerSlots));
|
|
|
+ }
|
|
|
+
|
|
|
+ var task = Task.Run(() =>
|
|
|
+ {
|
|
|
+ var session = new NetworkSession(sessionType, maxGamers, privateGamerSlots, true);
|
|
|
+ session.sessionState = NetworkSessionState.Lobby;
|
|
|
+ return session;
|
|
|
+ });
|
|
|
+
|
|
|
+ return new AsyncResultWrapper<NetworkSession>(task, null, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Ends the create session operation.
|
|
|
+ /// </summary>
|
|
|
+ public static NetworkSession EndCreate(IAsyncResult asyncResult)
|
|
|
+ {
|
|
|
+ if (asyncResult is AsyncResultWrapper<NetworkSession> wrapper)
|
|
|
+ {
|
|
|
+ return wrapper.GetResult();
|
|
|
+ }
|
|
|
+ throw new ArgumentException("Invalid async result", nameof(asyncResult));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Begins finding available network sessions.
|
|
|
+ /// </summary>
|
|
|
+ public static IAsyncResult BeginFind(
|
|
|
+ NetworkSessionType sessionType,
|
|
|
+ int maxLocalGamers,
|
|
|
+ NetworkSessionProperties searchProperties,
|
|
|
+ AsyncCallback callback,
|
|
|
+ object asyncState)
|
|
|
+ {
|
|
|
+ var task = Task.Run(() =>
|
|
|
+ {
|
|
|
+ // Mock implementation - return empty collection for now
|
|
|
+ // In a real implementation, this would search for available sessions
|
|
|
+ return new AvailableNetworkSessionCollection();
|
|
|
+ });
|
|
|
+
|
|
|
+ return new AsyncResultWrapper<AvailableNetworkSessionCollection>(task, callback, asyncState);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IAsyncResult BeginFind(
|
|
|
+ NetworkSessionType sessionType,
|
|
|
+ IEnumerable<SignedInGamer> localGamers,
|
|
|
+ NetworkSessionProperties searchProperties,
|
|
|
+ AsyncCallback callback = null,
|
|
|
+ object asyncState = null
|
|
|
+ )
|
|
|
+ {
|
|
|
+ var task = Task.Run(() =>
|
|
|
+ {
|
|
|
+ // Mock implementation - return empty collection for now
|
|
|
+ // In a real implementation, this would search for available sessions
|
|
|
+ return new AvailableNetworkSessionCollection();
|
|
|
+ });
|
|
|
+
|
|
|
+ return new AsyncResultWrapper<AvailableNetworkSessionCollection>(task, callback, asyncState);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Ends the find sessions operation.
|
|
|
+ /// </summary>
|
|
|
+ public static AvailableNetworkSessionCollection EndFind(IAsyncResult asyncResult)
|
|
|
+ {
|
|
|
+ if (asyncResult is AsyncResultWrapper<AvailableNetworkSessionCollection> wrapper)
|
|
|
+ {
|
|
|
+ return wrapper.GetResult();
|
|
|
+ }
|
|
|
+ throw new ArgumentException("Invalid async result", nameof(asyncResult));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Begins joining a network session.
|
|
|
+ /// </summary>
|
|
|
+ public static IAsyncResult BeginJoin(
|
|
|
+ AvailableNetworkSession availableSession,
|
|
|
+ AsyncCallback callback,
|
|
|
+ object asyncState)
|
|
|
+ {
|
|
|
+ var task = Task.Run(() =>
|
|
|
+ {
|
|
|
+ // Mock implementation - create a new session as a client
|
|
|
+ var session = new NetworkSession(availableSession.SessionType, availableSession.CurrentGamerCount, 0, false);
|
|
|
+ session.sessionState = NetworkSessionState.Lobby;
|
|
|
+ return session;
|
|
|
+ });
|
|
|
+
|
|
|
+ return new AsyncResultWrapper<NetworkSession>(task, callback, asyncState);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Ends the join session operation.
|
|
|
+ /// </summary>
|
|
|
+ public static NetworkSession EndJoin(IAsyncResult asyncResult)
|
|
|
+ {
|
|
|
+ if (asyncResult is AsyncResultWrapper<NetworkSession> wrapper)
|
|
|
+ {
|
|
|
+ return wrapper.GetResult();
|
|
|
+ }
|
|
|
+ throw new ArgumentException("Invalid async result", nameof(asyncResult));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Begins joining an invited session.
|
|
|
+ /// </summary>
|
|
|
+ public static IAsyncResult BeginJoinInvited(int maxLocalGamers, AsyncCallback callback, object asyncState)
|
|
|
+ {
|
|
|
+ var task = Task.Run(() =>
|
|
|
+ {
|
|
|
+ // Mock implementation
|
|
|
+ var session = new NetworkSession(NetworkSessionType.PlayerMatch, 8, 0, false);
|
|
|
+ session.sessionState = NetworkSessionState.Lobby;
|
|
|
+ // Fire invite accepted event - pass a mock gamer
|
|
|
+ InviteAccepted?.Invoke(null, new InviteAcceptedEventArgs(GamerServices.SignedInGamer.Current, false));
|
|
|
+ return session;
|
|
|
+ });
|
|
|
+
|
|
|
+ return new AsyncResultWrapper<NetworkSession>(task, callback, asyncState);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IAsyncResult BeginJoinInvited(
|
|
|
+ IEnumerable<SignedInGamer> localGamers,
|
|
|
+ AsyncCallback callback,
|
|
|
+ object asyncState
|
|
|
+ )
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Ends the join invited session operation.
|
|
|
+ /// </summary>
|
|
|
+ public static NetworkSession EndJoinInvited(IAsyncResult asyncResult)
|
|
|
+ {
|
|
|
+ return EndJoin(asyncResult);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Creates a new network session synchronously.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sessionType">The type of session to create.</param>
|
|
|
+ /// <param name="maxLocalGamers">Maximum number of local gamers.</param>
|
|
|
+ /// <param name="maxGamers">Maximum total number of gamers.</param>
|
|
|
+ /// <param name="privateGamerSlots">Number of private gamer slots.</param>
|
|
|
+ /// <param name="sessionProperties">Session properties.</param>
|
|
|
+ /// <returns>The created network session.</returns>
|
|
|
+ public static NetworkSession Create(NetworkSessionType sessionType, int maxLocalGamers, int maxGamers, int privateGamerSlots, IDictionary<string, object> sessionProperties)
|
|
|
+ {
|
|
|
+ var props = new NetworkSessionProperties();
|
|
|
+ if (sessionProperties != null)
|
|
|
+ {
|
|
|
+ foreach (var kvp in sessionProperties)
|
|
|
+ {
|
|
|
+ props[kvp.Key] = kvp.Value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var asyncResult = BeginCreate(sessionType, maxLocalGamers, maxGamers, privateGamerSlots, props);
|
|
|
+ return EndCreate(asyncResult);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Creates a new network session synchronously with default properties.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sessionType">The type of session to create.</param>
|
|
|
+ /// <param name="maxLocalGamers">Maximum number of local gamers.</param>
|
|
|
+ /// <param name="maxGamers">Maximum total number of gamers.</param>
|
|
|
+ /// <returns>The created network session.</returns>
|
|
|
+ public static NetworkSession Create(NetworkSessionType sessionType, int maxLocalGamers, int maxGamers)
|
|
|
+ {
|
|
|
+ return Create(sessionType, maxLocalGamers, maxGamers, 0, new Dictionary<string, object>());
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Finds available network sessions synchronously.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sessionType">The type of sessions to find.</param>
|
|
|
+ /// <param name="maxLocalGamers">Maximum number of local gamers.</param>
|
|
|
+ /// <param name="sessionProperties">Session properties to search for.</param>
|
|
|
+ /// <returns>Collection of available sessions.</returns>
|
|
|
+ public static AvailableNetworkSessionCollection Find(NetworkSessionType sessionType, int maxLocalGamers, IDictionary<string, object> sessionProperties)
|
|
|
+ {
|
|
|
+ var props = new NetworkSessionProperties();
|
|
|
+ if (sessionProperties != null)
|
|
|
+ {
|
|
|
+ foreach (var kvp in sessionProperties)
|
|
|
+ {
|
|
|
+ props[kvp.Key] = kvp.Value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var asyncResult = BeginFind(sessionType, maxLocalGamers, props, null, null);
|
|
|
+ return EndFind(asyncResult);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Finds available network sessions synchronously with default properties.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sessionType">The type of sessions to find.</param>
|
|
|
+ /// <param name="maxLocalGamers">Maximum number of local gamers.</param>
|
|
|
+ /// <returns>Collection of available sessions.</returns>
|
|
|
+ public static AvailableNetworkSessionCollection Find(NetworkSessionType sessionType, int maxLocalGamers)
|
|
|
+ {
|
|
|
+ return Find(sessionType, maxLocalGamers, new Dictionary<string, object>());
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Joins an available network session synchronously.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="availableSession">The session to join.</param>
|
|
|
+ /// <returns>The joined network session.</returns>
|
|
|
+ public static NetworkSession Join(AvailableNetworkSession availableSession)
|
|
|
+ {
|
|
|
+ var asyncResult = BeginJoin(availableSession, null, null);
|
|
|
+ return EndJoin(asyncResult);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Updates the network session.
|
|
|
+ /// </summary>
|
|
|
+ public void Update()
|
|
|
+ {
|
|
|
+ if (disposed)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // Process any pending network messages
|
|
|
+ ProcessIncomingMessages();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Sends data to all gamers in the session.
|
|
|
+ /// </summary>
|
|
|
+ public void SendToAll(PacketWriter writer, SendDataOptions options)
|
|
|
+ {
|
|
|
+ SendToAll(writer, options, NetworkGamer.LocalGamer);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Sends data to all gamers except the specified sender.
|
|
|
+ /// </summary>
|
|
|
+ public void SendToAll(PacketWriter writer, SendDataOptions options, NetworkGamer sender)
|
|
|
+ {
|
|
|
+ if (writer == null)
|
|
|
+ throw new ArgumentNullException(nameof(writer));
|
|
|
+
|
|
|
+ byte[] data = writer.GetData();
|
|
|
+
|
|
|
+ lock (lockObject)
|
|
|
+ {
|
|
|
+ foreach (var gamer in gamers)
|
|
|
+ {
|
|
|
+ if (gamer != sender && !gamer.IsLocal)
|
|
|
+ {
|
|
|
+ SendDataToGamer(gamer, data, options);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Starts the game.
|
|
|
+ /// </summary>
|
|
|
+ public void StartGame()
|
|
|
+ {
|
|
|
+ if (sessionState == NetworkSessionState.Lobby)
|
|
|
+ {
|
|
|
+ sessionState = NetworkSessionState.Playing;
|
|
|
+ OnGameStarted();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Ends the game and returns to lobby.
|
|
|
+ /// </summary>
|
|
|
+ public void EndGame()
|
|
|
+ {
|
|
|
+ if (sessionState == NetworkSessionState.Playing)
|
|
|
+ {
|
|
|
+ sessionState = NetworkSessionState.Lobby;
|
|
|
+ OnGameEnded();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Notifies when a gamer's readiness changes.
|
|
|
+ /// </summary>
|
|
|
+ internal void NotifyReadinessChanged(NetworkGamer gamer)
|
|
|
+ {
|
|
|
+ // Send readiness update to other players
|
|
|
+ if (IsHost)
|
|
|
+ {
|
|
|
+ var writer = new PacketWriter();
|
|
|
+ writer.Write("ReadinessUpdate");
|
|
|
+ writer.Write(gamer.Id);
|
|
|
+ writer.Write(gamer.IsReady);
|
|
|
+ SendToAll(writer, SendDataOptions.Reliable, gamer);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Sends data to a specific gamer.
|
|
|
+ /// </summary>
|
|
|
+ internal void SendDataToGamer(NetworkGamer gamer, PacketWriter writer, SendDataOptions options)
|
|
|
+ {
|
|
|
+ SendDataToGamer(gamer, writer.GetData(), options);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void SendDataToGamer(NetworkGamer gamer, byte[] data, SendDataOptions options)
|
|
|
+ {
|
|
|
+ if (gamerEndpoints.TryGetValue(gamer.Id, out IPEndPoint endpoint))
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ udpClient.Send(data, data.Length, endpoint);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ // Log the error but don't crash the game
|
|
|
+ System.Diagnostics.Debug.WriteLine($"Failed to send data to {gamer.Gamertag}: {ex.Message}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AddGamer(NetworkGamer gamer)
|
|
|
+ {
|
|
|
+ lock (lockObject)
|
|
|
+ {
|
|
|
+ gamers.Add(gamer);
|
|
|
+ }
|
|
|
+ OnGamerJoined(gamer);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void RemoveGamer(NetworkGamer gamer)
|
|
|
+ {
|
|
|
+ lock (lockObject)
|
|
|
+ {
|
|
|
+ gamers.Remove(gamer);
|
|
|
+ gamerEndpoints.Remove(gamer.Id);
|
|
|
+ }
|
|
|
+ OnGamerLeft(gamer);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ProcessIncomingMessages()
|
|
|
+ {
|
|
|
+ // Mock implementation - in a real scenario this would process UDP messages
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnGameStarted()
|
|
|
+ {
|
|
|
+ GameStarted?.Invoke(this, new GameStartedEventArgs());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnGameEnded()
|
|
|
+ {
|
|
|
+ GameEnded?.Invoke(this, new GameEndedEventArgs());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnGamerJoined(NetworkGamer gamer)
|
|
|
+ {
|
|
|
+ GamerJoined?.Invoke(this, new GamerJoinedEventArgs(gamer));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnGamerLeft(NetworkGamer gamer)
|
|
|
+ {
|
|
|
+ GamerLeft?.Invoke(this, new GamerLeftEventArgs(gamer));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnSessionEnded(NetworkSessionEndReason reason)
|
|
|
+ {
|
|
|
+ sessionState = NetworkSessionState.Ended;
|
|
|
+ SessionEnded?.Invoke(this, new NetworkSessionEndedEventArgs(reason));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Disposes the network session.
|
|
|
+ /// </summary>
|
|
|
+ public void Dispose()
|
|
|
+ {
|
|
|
+ if (!disposed)
|
|
|
+ {
|
|
|
+ cancellationTokenSource?.Cancel();
|
|
|
+ receiveTask?.Wait(1000); // Wait up to 1 second
|
|
|
+
|
|
|
+ udpClient?.Close();
|
|
|
+ udpClient?.Dispose();
|
|
|
+ cancellationTokenSource?.Dispose();
|
|
|
+
|
|
|
+ OnSessionEnded(NetworkSessionEndReason.ClientSignedOut);
|
|
|
+ disposed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|