2 using System.Collections.Generic;
5 using Microsoft.Xna.Framework.Net;
6 using System.Diagnostics;
7 using Microsoft.Xna.Framework.GamerServices;
8 using Microsoft.Xna.Framework.Graphics;
9 using Microsoft.Xna.Framework;
10 using Microsoft.Xna.Framework.Input;
11 using System.Collections;
13 namespace CS_3505_Project_06
16 /// A manager class to handle network interactions between peers and
17 /// lobby/game switching.
19 public class NetworkGame
21 // Public methods and properties
22 #region Public Methods
25 /// Called when a session has been created or joined using CreateSession() or JoinSession().
27 /// <param name="session">The new session that was created or joined.</param>
28 /// <param name="networkGame">The NetworkGame that joined the session.</param>
29 public delegate void JoinedSessionDelegate(NetworkSession session, NetworkGame networkGame);
32 /// Called when sessions are found as a result of calling FindSessions().
34 /// <param name="sessions">A container of the available sessions.</param>
35 /// <param name="networkGame">The NetworkGame that searched for the sessions.</param>
36 public delegate void FoundSessionsDelegate(AvailableNetworkSessionCollection sessions, NetworkGame networkGame);
40 /// Called when an exception is thrown during an asynchronous operation.
42 /// <param name="exception">The exception that was thrown.</param>
43 /// <param name="networkGame">The NetworkGame that errored.</param>
44 public delegate void CaughtErrorDelegate(Exception exception, NetworkGame networkGame);
47 /// Get and set the error delegate, called when an exception is thrown during
48 /// and asynchronous operation. This will occur if you try to create or join a
49 /// session without being logged into a profile.
51 public CaughtErrorDelegate ErrorDelegate;
55 /// Construct a NetworkGame with a lobby and a game.
57 /// <param name="lobby">Provides an associated lobby to update and draw.</param>
58 /// <param name="game">Provides a game object to be played over the network.</param>
59 public NetworkGame(ILobby lobby, IDeterministicGame game)
61 Debug.Assert(lobby != null && game != null);
69 /// Get the Gamer object for the local player.
71 public LocalNetworkGamer LocalGamer
75 // TODO: Is this the correct way to get the single local gamer?
76 return mNetworkSession.LocalGamers[0];
81 /// Get all the gamers associated with the active network session.
83 public GamerCollection<NetworkGamer> NetworkGamers
87 return mNetworkSession.AllGamers;
93 /// Begin a new network session with the local gamer as the host. You must not
94 /// call this method or use JoinSession without first using LeaveSession.
96 /// <param name="callback">The delegate/method to call when the session is created.</param>
97 public void CreateSession(JoinedSessionDelegate callback)
99 CreateSession(mGame.MaximumSupportedPlayers, callback);
103 /// Begin a new network session with the local gamer as the host. You must not
104 /// call this method or use JoinSession without first using LeaveSession.
106 /// <param name="maxGamers">Provide the maximum number of players allowed to connect.</param>
107 /// <param name="callback">The delegate/method to call when the session is created.</param>
108 public void CreateSession(int maxGamers, JoinedSessionDelegate callback)
110 Debug.Assert(mNetworkSession == null);
112 mJoinedSessionDelegate = callback;
113 NetworkSession.BeginCreate(NetworkSessionType.SystemLink, 1, maxGamers, CreateSessionEnd, null);
115 private void CreateSessionEnd(IAsyncResult result)
117 Debug.Assert(mNetworkSession == null);
121 mNetworkSession = NetworkSession.EndCreate(result);
122 mNetworkSession.AllowHostMigration = true;
123 mNetworkSession.AllowJoinInProgress = false;
124 mNetworkSession.GameStarted += new EventHandler<GameStartedEventArgs>(mNetworkSession_GameStarted);
128 if (ErrorDelegate != null) ErrorDelegate(e, this);
131 mJoinedSessionDelegate(mNetworkSession, this);
132 mJoinedSessionDelegate = null;
137 void mNetworkSession_GameStarted(object sender, GameStartedEventArgs e)
143 /// Determine whether or not the network game object is associated with any network session.
145 /// <returns>True if there exists a NetworkSession; false otherwise.</returns>
146 public bool HasActiveSession
150 return mNetworkSession != null;
156 /// Find available sessions to join. You should not already be in a session when
157 /// calling this method; call LeaveSession first.
159 /// <param name="callback">The delegate/method to call when the search finishes.</param>
160 public void FindSessions(FoundSessionsDelegate callback)
162 Debug.Assert(mNetworkSession == null);
164 mFoundSessionsDelegate = callback;
165 NetworkSession.BeginFind(NetworkSessionType.SystemLink, 1, null, new AsyncCallback(FindSessionsEnd), null);
167 private void FindSessionsEnd(IAsyncResult result)
169 AvailableNetworkSessionCollection sessions;
172 sessions = NetworkSession.EndFind(result);
176 if (ErrorDelegate != null) ErrorDelegate(e, this);
179 mFoundSessionsDelegate(sessions, this);
180 mFoundSessionsDelegate = null;
184 /// Join a network session found using FindSessions(). This is for joining a game that
185 /// somebody else has already started hosting. You must not already be in a session.
187 /// <param name="availableSession">Pass the session object to try to join.</param>
188 /// <param name="callback">The delegate/method to call when the search finishes.</param>
189 public void JoinSession(AvailableNetworkSession availableSession, JoinedSessionDelegate callback)
191 Debug.Assert(mNetworkSession == null);
193 mJoinedSessionDelegate = callback;
194 NetworkSession.BeginJoin(availableSession, JoinSessionEnd, null);
196 private void JoinSessionEnd(IAsyncResult result)
198 Debug.Assert(mNetworkSession == null);
202 mNetworkSession = NetworkSession.EndJoin(result);
203 mNetworkSession.GameStarted += new EventHandler<GameStartedEventArgs>(mNetworkSession_GameStarted);
207 if (ErrorDelegate != null) ErrorDelegate(e, this);
210 mJoinedSessionDelegate(mNetworkSession, this);
211 mJoinedSessionDelegate = null;
216 /// Leave and dispose of any currently associated network session. You will find yourself
217 /// back in the lobby. You must already be in a session to leave it.
219 public void LeaveSession()
221 Debug.Assert(mNetworkSession != null);
223 mNetworkSession.Dispose();
224 mNetworkSession = null;
229 /// Set up the network session to simulate 200ms latency and 10% packet loss.
231 public void SimulateBadNetwork()
233 Debug.Assert(mNetworkSession != null);
235 mNetworkSession.SimulatedLatency = new TimeSpan(0, 0, 0, 0, 200);
236 mNetworkSession.SimulatedPacketLoss = 0.1f;
241 /// Indicate that the game should begin (moving players from the lobby to the game).
242 /// You must call CreateSession() before calling this.
244 public void StartGame()
246 Debug.Assert(mNetworkSession != null && mNetworkSession.IsHost &&
247 mNetworkSession.AllGamers.Count >= mGame.MinimumSupportedPlayers &&
248 mNetworkSession.IsEveryoneReady);
254 /// Indicate that the game should begin. This is like StartGame() without the sanity
255 /// checks. Use this for debugging.
257 public void ForceStartGame()
259 mNetworkSession.StartGame();
260 mNetworkSession.ResetReady();
267 /// Manages the network session and allows either the lobby or game to update.
269 /// <param name="gameTime">Pass the time away.</param>
270 public void Update(GameTime gameTime)
272 if (mNetworkSession == null)
274 mLobby.Update(gameTime, this);
278 mNetworkSession.Update();
279 HandleIncomingPackets();
281 if (mNetworkSession.SessionState == NetworkSessionState.Lobby)
283 mLobby.Update(gameTime, this);
285 else if (mNetworkSession.SessionState == NetworkSessionState.Playing)
287 if (mGame.IsGameOver(LocalGamerInfo) || mGame.IsTerminated(LocalGamerInfo))
289 // TODO: Should support moving back to the session lobby.
294 if (HaveNeededEvents)
296 if (IsLatencyAdjustmentFrame)
301 mLocalEvents.AddRange(GetEventsFromInput());
304 mGame.Update(mTargetTimeSpan);
310 if (mStallCount % 60 == 0)
312 Console.WriteLine("Stalled for " + mStallCount + " frames.");
315 /*if (mStallCount > StallTimeout)
320 else if (mStallCount == 1)
324 else if (mStallCount % 60 == 0)
333 /// Allows either the lobby or the game to draw, depending on the state
334 /// of the network connection and whether or not a game is in progress.
336 /// <param name="gameTime">Pass the time away.</param>
337 /// <param name="spriteBatch">The sprite batch.</param>
338 public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
340 if (mNetworkSession == null)
342 mLobby.Draw(spriteBatch);
346 if (mNetworkSession.SessionState == NetworkSessionState.Lobby)
348 mLobby.Draw(spriteBatch);
350 else if (mNetworkSession.SessionState == NetworkSessionState.Playing)
352 mGame.Draw(spriteBatch);
359 /// Get the chat messages that have been receive since the last time this
360 /// method was called.
362 /// <returns>List container of the chat messages.</returns>
363 public List<ChatInfo> ReceiveChats()
365 List<ChatInfo> chats = mChatPackets;
366 mChatPackets = new List<ChatInfo>();
371 /// Send a chat message to all gamers in the session. You should already be
372 /// in a session before calling this method.
374 /// <param name="message">The text of the message.</param>
375 public void SendChat(String message)
377 WriteChatPacket(message);
378 LocalGamer.SendData(mPacketWriter, SendDataOptions.ReliableInOrder);
382 /// Send a chat message to a specific gamer in the session. You should already
383 /// be in a session before calling this method.
385 /// <param name="message">The text of the message.</param>
386 /// <param name="recipient">The gamer to receive the message.</param>
387 public void SendChat(String message, NetworkGamer recipient)
389 WriteChatPacket(message);
390 LocalGamer.SendData(mPacketWriter, SendDataOptions.ReliableInOrder, recipient);
396 // Private class variable members
397 #region Instance Variables
399 NetworkSession mNetworkSession;
400 PacketReader mPacketReader = new PacketReader();
401 PacketWriter mPacketWriter = new PacketWriter();
403 JoinedSessionDelegate mJoinedSessionDelegate;
404 FoundSessionsDelegate mFoundSessionsDelegate;
407 IDeterministicGame mGame;
409 List<ChatInfo> mChatPackets = new List<ChatInfo>();
411 List<EventInfo> mLocalEvents = new List<EventInfo>();
412 List<EventInfo> mLastLocalEvents = new List<EventInfo>();
414 List<Keys> mLastPressedKeys = new List<Keys>();
415 bool mLastLeftButtonPressed;
416 bool mLastRightButtonPressed;
417 bool mLastMiddleButtonPressed;
418 int mLastMousePositionX;
419 int mLastMousePositionY;
422 long mNextLatencyAdjustmentFrame;
426 TimeSpan mTargetTimeSpan = new TimeSpan(166666);
427 public TimeSpan TargetTimeSpan
431 return mTargetTimeSpan;
435 Dictionary<byte, GamerInfo> mGamers;
436 GamerInfo[] GamerArray
440 GamerInfo[] gamerList = mGamers.Values.ToArray();
441 Array.Sort(gamerList, delegate(GamerInfo a, GamerInfo b)
443 return a.Gamer.Id.CompareTo(b.Gamer.Id);
448 GamerInfo LocalGamerInfo
452 return mGamers[LocalGamer.Id];
459 // Private types for the implementation of the network protocol
460 #region Private Types
485 abstract class EventInfo
487 public NetworkGamer Gamer;
488 public long FrameOfApplication;
490 public EventInfo(NetworkGamer gamer, long frameNumber)
493 FrameOfApplication = frameNumber;
496 public abstract EventType Id
502 class KeyboardEventInfo : EventInfo
505 public bool IsKeyDown;
507 public KeyboardEventInfo(NetworkGamer gamer, long frameNumber, Keys key, bool isDown)
508 : base(gamer, frameNumber)
514 public override EventType Id
516 get { return IsKeyDown ? EventType.KeyDown : EventType.KeyUp; }
520 class MouseButtonEventInfo : EventInfo
522 public MouseButton Button;
523 public bool IsButtonDown;
525 public MouseButtonEventInfo(NetworkGamer gamer, long frameNumber, MouseButton button, bool isDown)
526 : base(gamer, frameNumber)
529 IsButtonDown = isDown;
532 public override EventType Id
534 get { return IsButtonDown ? EventType.MouseDown : EventType.MouseUp; }
538 class MouseMotionEventInfo : EventInfo
543 public MouseMotionEventInfo(NetworkGamer gamer, long frameNumber, int x, int y)
544 : base(gamer, frameNumber)
550 public override EventType Id
552 get { return EventType.MouseMove; }
558 public NetworkGamer Gamer;
559 public long HighestFrameNumber = 0;
560 public int StallCount = 0;
561 public int AverageOwd = 0;
562 public bool IsWaitedOn = false;
563 public List<EventInfo>[] Events = new List<EventInfo>[MaximumLatency];
565 public GamerInfo(NetworkGamer gamer)
571 const int MaximumLatency = 120;
572 const int StallTimeout = 900;
577 // Private implementation methods of the network protocol
578 #region Private Implementation Methods
581 /// Reinitialize the private variables in preparation for a new game to start.
586 mNextLatencyAdjustmentFrame = 1;
588 mAverageOwd = CurrentAverageOneWayDelay;
590 mGamers = new Dictionary<byte, GamerInfo>();
591 foreach (NetworkGamer gamer in NetworkGamers)
593 mGamers.Add(gamer.Id, new GamerInfo(gamer));
596 mGame.ResetGame(GamerArray, LocalGamerInfo);
600 void HandleIncomingPackets()
602 LocalNetworkGamer localGamer = LocalGamer;
604 while (localGamer.IsDataAvailable)
608 localGamer.ReceiveData(mPacketReader, out sender);
609 GamerInfo senderInfo = mGamers[sender.Id];
611 PacketType packetId = (PacketType)mPacketReader.ReadByte();
614 case PacketType.Chat:
616 short messageLength = mPacketReader.ReadInt16();
617 char[] message = mPacketReader.ReadChars(messageLength);
619 ChatInfo chatPacket = new ChatInfo(sender, new String(message));
620 mChatPackets.Add(chatPacket);
623 case PacketType.Event:
625 short stallCount = mPacketReader.ReadInt16();
626 short averageOwd = mPacketReader.ReadInt16();
627 int frameNumber = mPacketReader.ReadInt32();
628 byte numEvents = mPacketReader.ReadByte();
630 if (frameNumber <= senderInfo.HighestFrameNumber)
632 // we know about all these events, so don't bother reading them
636 for (byte i = 0; i < numEvents; ++i)
638 EventInfo eventInfo = ReadEvent(mPacketReader, sender);
640 if (eventInfo != null && eventInfo.FrameOfApplication < senderInfo.HighestFrameNumber)
642 int index = EventArrayIndex;
643 if (senderInfo.Events[index] == null) senderInfo.Events[index] = new List<EventInfo>();
644 senderInfo.Events[index].Add(eventInfo);
648 senderInfo.StallCount = stallCount;
649 senderInfo.AverageOwd = averageOwd;
650 senderInfo.HighestFrameNumber = frameNumber;
653 case PacketType.Stall:
655 byte numStalledPeers = mPacketReader.ReadByte();
656 byte[] stalledPeers = mPacketReader.ReadBytes(numStalledPeers);
664 Console.WriteLine("Received unknown packet type: " + (int)packetId);
673 get { return (int)(mGame.CurrentFrameNumber % MaximumLatency); }
676 EventInfo ReadEvent(PacketReader packetReader, NetworkGamer sender)
678 EventType eventId = (EventType)packetReader.ReadByte();
679 long frameNumber = packetReader.ReadInt32();
683 case EventType.KeyDown:
685 int keyCode1 = packetReader.ReadInt32();
686 return new KeyboardEventInfo(sender, frameNumber, (Keys)keyCode1, true);
688 case EventType.KeyUp:
690 int keyCode2 = packetReader.ReadInt32();
691 return new KeyboardEventInfo(sender, frameNumber, (Keys)keyCode2, false);
693 case EventType.MouseDown:
695 byte buttonId1 = packetReader.ReadByte();
696 return new MouseButtonEventInfo(sender, frameNumber, (MouseButton)buttonId1, true);
698 case EventType.MouseUp:
700 byte buttonId2 = packetReader.ReadByte();
701 return new MouseButtonEventInfo(sender, frameNumber, (MouseButton)buttonId2, false);
703 case EventType.MouseMove:
705 short x = packetReader.ReadInt16();
706 short y = packetReader.ReadInt16();
707 return new MouseMotionEventInfo(sender, frameNumber, x, y);
711 Console.WriteLine("Received unknown event type: " + (int)eventId);
717 void WriteChatPacket(String message)
719 mPacketWriter.Write((byte)PacketType.Chat);
720 mPacketWriter.Write((short)message.Length);
721 mPacketWriter.Write(message.ToCharArray());
724 void WriteEventPacket(List<EventInfo> events)
726 mPacketWriter.Write((byte)PacketType.Event);
727 mPacketWriter.Write((short)mStallCount);
728 mPacketWriter.Write((short)mAverageOwd);
729 mPacketWriter.Write((int)(mGame.CurrentFrameNumber + mLatency));
730 mPacketWriter.Write((byte)events.Count);
732 foreach (EventInfo eventInfo in events)
734 mPacketWriter.Write((byte)eventInfo.Id);
735 mPacketWriter.Write((int)eventInfo.FrameOfApplication);
737 KeyboardEventInfo keyboardEventInfo = eventInfo as KeyboardEventInfo;
738 if (keyboardEventInfo != null)
740 mPacketWriter.Write((int)keyboardEventInfo.Key);
744 MouseButtonEventInfo mouseButtonEventInfo = eventInfo as MouseButtonEventInfo;
745 if (mouseButtonEventInfo != null)
747 mPacketWriter.Write((byte)mouseButtonEventInfo.Button);
751 MouseMotionEventInfo mouseMotionEventInfo = eventInfo as MouseMotionEventInfo;
752 if (mouseMotionEventInfo != null)
754 mPacketWriter.Write((short)mouseMotionEventInfo.X);
755 mPacketWriter.Write((short)mouseMotionEventInfo.Y);
762 bool IsLatencyAdjustmentFrame
766 return mNextLatencyAdjustmentFrame == mGame.CurrentFrameNumber;
772 Debug.Assert(IsLatencyAdjustmentFrame);
774 int maxStallCount = 0;
775 int maxAverageOwd = 0;
777 foreach (GamerInfo gamerInfo in GamerArray)
779 if (gamerInfo.StallCount > maxStallCount) maxStallCount = gamerInfo.StallCount;
780 if (gamerInfo.AverageOwd > maxAverageOwd) maxAverageOwd = gamerInfo.AverageOwd;
784 int prevLatency = mLatency;
786 if (maxStallCount > 0)
788 mLatency += maxStallCount;
792 mLatency = (int)(0.6 * (double)(mLatency - maxAverageOwd) + 1.0);
796 if (prevLatency != mLatency) Console.WriteLine("Latency readjusted to " + mLatency);
798 if (mLatency < 1) mLatency = 1;
799 if (mLatency > MaximumLatency) mLatency = MaximumLatency;
801 mNextLatencyAdjustmentFrame = mGame.CurrentFrameNumber + mLatency;
802 mAverageOwd = CurrentAverageOneWayDelay;
804 mLastLocalEvents = mLocalEvents;
805 mLocalEvents = new List<EventInfo>();
810 List<EventInfo> GetEventsFromInput()
812 List<EventInfo> events = new List<EventInfo>();
814 // 1. Find the keyboard differences; written by Peter.
816 KeyboardState keyState = Keyboard.GetState();
818 List<Keys> pressedKeys = new List<Keys>();
819 List<Keys> releasedKeys = new List<Keys>();
821 Keys[] pressedKeysArray = keyState.GetPressedKeys();
822 foreach (Keys k in pressedKeysArray)
824 if (!mLastPressedKeys.Contains(k)) pressedKeys.Add(k);
825 else mLastPressedKeys.Remove(k);
828 releasedKeys = mLastPressedKeys;
830 foreach (Keys key in pressedKeys)
832 events.Add(new KeyboardEventInfo(LocalGamer, mGame.CurrentFrameNumber, key, true));
834 foreach (Keys key in releasedKeys)
836 events.Add(new KeyboardEventInfo(LocalGamer, mGame.CurrentFrameNumber, key, false));
839 // 2. Find the mouse differences.
841 MouseState mouseState = Mouse.GetState();
843 bool leftButtonPressed = mouseState.LeftButton == ButtonState.Pressed;
844 if (leftButtonPressed != mLastLeftButtonPressed)
846 events.Add(new MouseButtonEventInfo(LocalGamer, mGame.CurrentFrameNumber, MouseButton.Left, leftButtonPressed));
849 bool rightButtonPressed = mouseState.LeftButton == ButtonState.Pressed;
850 if (rightButtonPressed != mLastRightButtonPressed)
852 events.Add(new MouseButtonEventInfo(LocalGamer, mGame.CurrentFrameNumber, MouseButton.Right, rightButtonPressed));
855 bool middleButtonPressed = mouseState.LeftButton == ButtonState.Pressed;
856 if (middleButtonPressed != mLastMiddleButtonPressed)
858 events.Add(new MouseButtonEventInfo(LocalGamer, mGame.CurrentFrameNumber, MouseButton.Middle, middleButtonPressed));
861 int mousePositionX = mouseState.X;
862 int mousePositionY = mouseState.Y;
863 if (mousePositionX != mLastMousePositionX || mousePositionY != mLastMousePositionY)
865 events.Add(new MouseMotionEventInfo(LocalGamer, mGame.CurrentFrameNumber, mousePositionX, mousePositionY));
868 // 3. Save the current peripheral state.
870 mLastPressedKeys = new List<Keys>(pressedKeysArray);
871 mLastLeftButtonPressed = leftButtonPressed;
872 mLastRightButtonPressed = rightButtonPressed;
873 mLastMiddleButtonPressed = middleButtonPressed;
874 mLastMousePositionX = mousePositionX;
875 mLastMousePositionY = mousePositionY;
880 void SendLocalEvents()
882 SendLocalEvents((NetworkGamer)null);
885 void SendLocalEvents(List<NetworkGamer> recipicents)
887 foreach (NetworkGamer gamer in recipicents)
889 SendLocalEvents(gamer);
893 void SendLocalEvents(NetworkGamer recipient)
895 List<EventInfo> events = new List<EventInfo>(mLocalEvents);
896 events.AddRange(mLastLocalEvents);
898 WriteEventPacket(events);
900 if (recipient != null)
902 LocalGamer.SendData(mPacketWriter, SendDataOptions.Reliable, recipient);
906 LocalGamer.SendData(mPacketWriter, SendDataOptions.None);
911 bool HaveNeededEvents
915 long currentFrame = mGame.CurrentFrameNumber;
917 foreach (GamerInfo gamerInfo in mGamers.Values)
919 if (gamerInfo.HighestFrameNumber < currentFrame) return false;
928 int index = EventArrayIndex;
930 foreach (GamerInfo gamerInfo in GamerArray)
932 if (gamerInfo.Events[index] == null) continue;
934 foreach (EventInfo eventInfo in gamerInfo.Events[index])
936 KeyboardEventInfo keyboardEventInfo = eventInfo as KeyboardEventInfo;
937 if (keyboardEventInfo != null)
939 mGame.ApplyKeyInput(gamerInfo, keyboardEventInfo.Key, keyboardEventInfo.IsKeyDown);
943 MouseButtonEventInfo mouseButtonEventInfo = eventInfo as MouseButtonEventInfo;
944 if (mouseButtonEventInfo != null)
946 mGame.ApplyMouseButtonInput(gamerInfo, mouseButtonEventInfo.IsButtonDown);
950 MouseMotionEventInfo mouseMotionEventInfo = eventInfo as MouseMotionEventInfo;
951 if (mouseMotionEventInfo != null)
953 mGame.ApplyMouseLocationInput(gamerInfo, mouseMotionEventInfo.X, mouseMotionEventInfo.Y);
958 gamerInfo.Events[index] = null;
963 int CurrentAverageOneWayDelay
967 Debug.Assert(mNetworkSession != null);
969 double numRemoteGamersTwice = 2 * mNetworkSession.RemoteGamers.Count;
970 double averageOwd = 0;
972 foreach (NetworkGamer gamer in mNetworkSession.RemoteGamers)
974 TimeSpan timeSpan = gamer.RoundtripTime;
975 averageOwd += timeSpan.TotalMilliseconds;
978 return (int)(averageOwd / numRemoteGamersTwice / 16.6666);