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 /// Construct a NetworkGame with a lobby and a game.
42 /// <param name="lobby">Provides an associated lobby to update and draw.</param>
43 /// <param name="game">Provides a game object to be played over the network.</param>
44 public NetworkGame(ILobby lobby, IDeterministicGame game)
46 Debug.Assert(lobby != null && game != null);
54 /// Get the Gamer object for the local player.
56 public LocalNetworkGamer LocalGamer
60 // TODO: Is this the correct way to get the single local gamer?
61 return mNetworkSession.LocalGamers[0];
66 /// Get all the gamers associated with the active network session.
68 public GamerCollection<NetworkGamer> NetworkGamers
72 return mNetworkSession.AllGamers;
78 /// Begin a new network session with the local gamer as the host. You must not
79 /// call this method or use JoinSession without first using LeaveSession.
81 /// <param name="callback">The delegate/method to call when the session is created.</param>
82 public void CreateSession(JoinedSessionDelegate callback)
84 CreateSession(mGame.MaximumSupportedPlayers, callback);
88 /// Begin a new network session with the local gamer as the host. You must not
89 /// call this method or use JoinSession without first using LeaveSession.
91 /// <param name="maxGamers">Provide the maximum number of players allowed to connect.</param>
92 /// <param name="callback">The delegate/method to call when the session is created.</param>
93 public void CreateSession(int maxGamers, JoinedSessionDelegate callback)
95 Debug.Assert(mNetworkSession == null);
97 mJoinedSessionDelegate = callback;
98 NetworkSession.BeginCreate(NetworkSessionType.SystemLink, 1, maxGamers, CreateSessionEnd, null);
100 private void CreateSessionEnd(IAsyncResult result)
102 Debug.Assert(mNetworkSession == null);
104 mNetworkSession = NetworkSession.EndCreate(result);
105 mNetworkSession.AllowHostMigration = true;
106 mNetworkSession.AllowJoinInProgress = false;
108 mJoinedSessionDelegate(mNetworkSession, this);
112 /// Determine whether or not the network game object is associated with any network session.
114 /// <returns>True if there exists a NetworkSession; false otherwise.</returns>
115 public bool HasActiveSession
119 return mNetworkSession != null;
125 /// Find available sessions to join. You should not already be in a session when
126 /// calling this method; call LeaveSession first.
128 /// <param name="callback">The delegate/method to call when the search finishes.</param>
129 public void FindSessions(FoundSessionsDelegate callback)
131 Debug.Assert(mNetworkSession == null);
133 mFoundSessionsDelegate = callback;
134 NetworkSession.BeginFind(NetworkSessionType.SystemLink, 1, null, new AsyncCallback(FindSessionsEnd), null);
136 private void FindSessionsEnd(IAsyncResult result)
138 AvailableNetworkSessionCollection sessions = NetworkSession.EndFind(result);
139 mFoundSessionsDelegate(sessions, this);
143 /// Join a network session found using FindSessions(). This is for joining a game that
144 /// somebody else has already started hosting. You must not already be in a session.
146 /// <param name="availableSession">Pass the session object to try to join.</param>
147 /// <param name="callback">The delegate/method to call when the search finishes.</param>
148 public void JoinSession(AvailableNetworkSession availableSession, JoinedSessionDelegate callback)
150 Debug.Assert(mNetworkSession == null);
152 mJoinedSessionDelegate = callback;
153 NetworkSession.BeginJoin(availableSession, JoinSessionEnd, null);
155 private void JoinSessionEnd(IAsyncResult result)
157 Debug.Assert(mNetworkSession == null);
159 mNetworkSession = NetworkSession.EndJoin(result);
161 mJoinedSessionDelegate(mNetworkSession, this);
162 mJoinedSessionDelegate = null;
167 /// Leave and dispose of any currently associated network session. You will find yourself
168 /// back in the lobby. You must already be in a session to leave it.
170 public void LeaveSession()
172 Debug.Assert(mNetworkSession != null);
174 mNetworkSession.Dispose();
175 mNetworkSession = null;
180 /// Set up the network session to simulate 200ms latency and 10% packet loss.
182 public void SimulateBadNetwork()
184 Debug.Assert(mNetworkSession != null);
186 mNetworkSession.SimulatedLatency = new TimeSpan(0, 0, 0, 0, 200);
187 mNetworkSession.SimulatedPacketLoss = 0.1f;
192 /// Indicate that the game should begin (moving players from the lobby to the game).
193 /// You must call CreateSession() before calling this.
195 public void StartGame()
197 Debug.Assert(mNetworkSession != null && mNetworkSession.IsHost &&
198 mNetworkSession.AllGamers.Count >= mGame.MinimumSupportedPlayers &&
199 mNetworkSession.IsEveryoneReady);
205 /// Indicate that the game should begin. This is like StartGame() without the sanity
206 /// checks. Use this for debugging.
208 public void ForceStartGame()
210 mNetworkSession.StartGame();
211 mNetworkSession.ResetReady();
218 /// Manages the network session and allows either the lobby or game to update.
220 /// <param name="gameTime">Pass the time away.</param>
221 public void Update(GameTime gameTime)
223 if (mNetworkSession == null)
225 mLobby.Update(gameTime, this);
229 mNetworkSession.Update();
230 HandleIncomingPackets();
232 if (mNetworkSession.SessionState == NetworkSessionState.Lobby)
234 mLobby.Update(gameTime, this);
236 //if state started as lobby and was changed to playing, call reset.
237 if (mNetworkSession.SessionState == NetworkSessionState.Playing)
240 else if (mNetworkSession.SessionState == NetworkSessionState.Playing)
242 //TODO reset needs to be called for all players, currently LocalGamerInfo throughs exception for all nonhosts
243 // because in start game reset is only called on hosts games thus other clients never get an updated list
247 //thoughs exeption see TODO above
248 //if (mGame.IsGameOver(LocalGamerInfo) || mGame.IsTerminated(LocalGamerInfo))
250 // // TODO: Should support moving back to the session lobby.
255 if (HaveNeededEvents)
257 if (IsLatencyAdjustmentFrame)
262 mLocalEvents.AddRange(GetEventsFromInput());
265 mGame.Update(mTargetTimeSpan);
271 if (mStallCount % 60 == 0)
273 Console.WriteLine("Stalled for " + mStallCount + " frames.");
276 /*if (mStallCount > StallTimeout)
281 else if (mStallCount == 1)
285 else if (mStallCount % 60 == 0)
294 /// Allows either the lobby or the game to draw, depending on the state
295 /// of the network connection and whether or not a game is in progress.
297 /// <param name="gameTime">Pass the time away.</param>
298 /// <param name="spriteBatch">The sprite batch.</param>
299 public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
301 if (mNetworkSession == null)
303 mLobby.Draw(spriteBatch);
307 if (mNetworkSession.SessionState == NetworkSessionState.Lobby)
309 mLobby.Draw(spriteBatch);
311 else if (mNetworkSession.SessionState == NetworkSessionState.Playing)
313 mGame.Draw(spriteBatch);
320 /// Get the chat messages that have been receive since the last time this
321 /// method was called.
323 /// <returns>List container of the chat messages.</returns>
324 public List<ChatInfo> ReceiveChats()
326 List<ChatInfo> chats = mChatPackets;
327 mChatPackets = new List<ChatInfo>();
332 /// Send a chat message to all gamers in the session. You should already be
333 /// in a session before calling this method.
335 /// <param name="message">The text of the message.</param>
336 public void SendChat(String message)
338 WriteChatPacket(message);
339 LocalGamer.SendData(mPacketWriter, SendDataOptions.ReliableInOrder);
343 /// Send a chat message to a specific gamer in the session. You should already
344 /// be in a session before calling this method.
346 /// <param name="message">The text of the message.</param>
347 /// <param name="recipient">The gamer to receive the message.</param>
348 public void SendChat(String message, NetworkGamer recipient)
350 WriteChatPacket(message);
351 LocalGamer.SendData(mPacketWriter, SendDataOptions.ReliableInOrder, recipient);
357 // Private class variable members
358 #region Instance Variables
360 NetworkSession mNetworkSession;
361 PacketReader mPacketReader = new PacketReader();
362 PacketWriter mPacketWriter = new PacketWriter();
364 JoinedSessionDelegate mJoinedSessionDelegate;
365 FoundSessionsDelegate mFoundSessionsDelegate;
368 IDeterministicGame mGame;
370 List<ChatInfo> mChatPackets = new List<ChatInfo>();
372 List<EventInfo> mLocalEvents = new List<EventInfo>();
373 List<EventInfo> mLastLocalEvents = new List<EventInfo>();
375 List<Keys> mLastPressedKeys = new List<Keys>();
376 bool mLastLeftButtonPressed;
377 bool mLastRightButtonPressed;
378 bool mLastMiddleButtonPressed;
379 int mLastMousePositionX;
380 int mLastMousePositionY;
383 long mNextLatencyAdjustmentFrame;
387 TimeSpan mTargetTimeSpan = new TimeSpan(166666);
388 public TimeSpan TargetTimeSpan
392 return mTargetTimeSpan;
396 Dictionary<byte, GamerInfo> mGamers;
397 GamerInfo[] GamerArray
401 GamerInfo[] gamerList = mGamers.Values.ToArray();
402 Array.Sort(gamerList, delegate(GamerInfo a, GamerInfo b)
404 return a.Gamer.Id.CompareTo(b.Gamer.Id);
409 GamerInfo LocalGamerInfo
413 return mGamers[LocalGamer.Id];
420 // Private types for the implementation of the network protocol
421 #region Private Types
446 abstract class EventInfo
448 public NetworkGamer Gamer;
449 public long FrameOfApplication;
451 public EventInfo(NetworkGamer gamer, long frameNumber)
454 FrameOfApplication = frameNumber;
457 public abstract EventType Id
463 class KeyboardEventInfo : EventInfo
466 public bool IsKeyDown;
468 public KeyboardEventInfo(NetworkGamer gamer, long frameNumber, Keys key, bool isDown)
469 : base(gamer, frameNumber)
475 public override EventType Id
477 get { return IsKeyDown ? EventType.KeyDown : EventType.KeyUp; }
481 class MouseButtonEventInfo : EventInfo
483 public MouseButton Button;
484 public bool IsButtonDown;
486 public MouseButtonEventInfo(NetworkGamer gamer, long frameNumber, MouseButton button, bool isDown)
487 : base(gamer, frameNumber)
490 IsButtonDown = isDown;
493 public override EventType Id
495 get { return IsButtonDown ? EventType.MouseDown : EventType.MouseUp; }
499 class MouseMotionEventInfo : EventInfo
504 public MouseMotionEventInfo(NetworkGamer gamer, long frameNumber, int x, int y)
505 : base(gamer, frameNumber)
511 public override EventType Id
513 get { return EventType.MouseMove; }
519 public NetworkGamer Gamer;
520 public long HighestFrameNumber = 0;
521 public int StallCount = 0;
522 public int AverageOwd = 0;
523 public bool IsWaitedOn = false;
524 public List<EventInfo>[] Events = new List<EventInfo>[MaximumLatency];
526 public GamerInfo(NetworkGamer gamer)
532 const int MaximumLatency = 120;
533 const int StallTimeout = 900;
538 // Private implementation methods of the network protocol
539 #region Private Implementation Methods
542 /// Reinitialize the private variables in preparation for a new game to start.
547 mNextLatencyAdjustmentFrame = 1;
549 mAverageOwd = CurrentAverageOneWayDelay;
551 mGamers = new Dictionary<byte, GamerInfo>();
552 foreach (NetworkGamer gamer in NetworkGamers)
554 mGamers.Add(gamer.Id, new GamerInfo(gamer));
557 mGame.ResetGame(GamerArray, LocalGamerInfo);
561 void HandleIncomingPackets()
563 LocalNetworkGamer localGamer = LocalGamer;
565 while (localGamer.IsDataAvailable)
569 localGamer.ReceiveData(mPacketReader, out sender);
570 GamerInfo senderInfo = mGamers[sender.Id];
572 PacketType packetId = (PacketType)mPacketReader.ReadByte();
575 case PacketType.Chat:
577 short messageLength = mPacketReader.ReadInt16();
578 char[] message = mPacketReader.ReadChars(messageLength);
580 ChatInfo chatPacket = new ChatInfo(sender, new String(message));
581 mChatPackets.Add(chatPacket);
584 case PacketType.Event:
586 short stallCount = mPacketReader.ReadInt16();
587 short averageOwd = mPacketReader.ReadInt16();
588 int frameNumber = mPacketReader.ReadInt32();
589 byte numEvents = mPacketReader.ReadByte();
591 if (frameNumber <= senderInfo.HighestFrameNumber)
593 // we know about all these events, so don't bother reading them
597 for (byte i = 0; i < numEvents; ++i)
599 EventInfo eventInfo = ReadEvent(mPacketReader, sender);
601 if (eventInfo != null && eventInfo.FrameOfApplication < senderInfo.HighestFrameNumber)
603 int index = EventArrayIndex;
604 if (senderInfo.Events[index] == null) senderInfo.Events[index] = new List<EventInfo>();
605 senderInfo.Events[index].Add(eventInfo);
609 senderInfo.StallCount = stallCount;
610 senderInfo.AverageOwd = averageOwd;
611 senderInfo.HighestFrameNumber = frameNumber;
614 case PacketType.Stall:
616 byte numStalledPeers = mPacketReader.ReadByte();
617 byte[] stalledPeers = mPacketReader.ReadBytes(numStalledPeers);
625 Console.WriteLine("Received unknown packet type: " + (int)packetId);
634 get { return (int)(mGame.CurrentFrameNumber % MaximumLatency); }
637 EventInfo ReadEvent(PacketReader packetReader, NetworkGamer sender)
639 EventType eventId = (EventType)packetReader.ReadByte();
640 long frameNumber = packetReader.ReadInt32();
644 case EventType.KeyDown:
646 int keyCode1 = packetReader.ReadInt32();
647 return new KeyboardEventInfo(sender, frameNumber, (Keys)keyCode1, true);
649 case EventType.KeyUp:
651 int keyCode2 = packetReader.ReadInt32();
652 return new KeyboardEventInfo(sender, frameNumber, (Keys)keyCode2, false);
654 case EventType.MouseDown:
656 byte buttonId1 = packetReader.ReadByte();
657 return new MouseButtonEventInfo(sender, frameNumber, (MouseButton)buttonId1, true);
659 case EventType.MouseUp:
661 byte buttonId2 = packetReader.ReadByte();
662 return new MouseButtonEventInfo(sender, frameNumber, (MouseButton)buttonId2, false);
664 case EventType.MouseMove:
666 short x = packetReader.ReadInt16();
667 short y = packetReader.ReadInt16();
668 return new MouseMotionEventInfo(sender, frameNumber, x, y);
672 Console.WriteLine("Received unknown event type: " + (int)eventId);
678 void WriteChatPacket(String message)
680 mPacketWriter.Write((byte)PacketType.Chat);
681 mPacketWriter.Write((short)message.Length);
682 mPacketWriter.Write(message.ToCharArray());
685 void WriteEventPacket(List<EventInfo> events)
687 mPacketWriter.Write((byte)PacketType.Event);
688 mPacketWriter.Write((short)mStallCount);
689 mPacketWriter.Write((short)mAverageOwd);
690 mPacketWriter.Write((int)(mGame.CurrentFrameNumber + mLatency));
691 mPacketWriter.Write((byte)events.Count);
693 foreach (EventInfo eventInfo in events)
695 mPacketWriter.Write((byte)eventInfo.Id);
696 mPacketWriter.Write((int)eventInfo.FrameOfApplication);
698 KeyboardEventInfo keyboardEventInfo = eventInfo as KeyboardEventInfo;
699 if (keyboardEventInfo != null)
701 mPacketWriter.Write((int)keyboardEventInfo.Key);
705 MouseButtonEventInfo mouseButtonEventInfo = eventInfo as MouseButtonEventInfo;
706 if (mouseButtonEventInfo != null)
708 mPacketWriter.Write((byte)mouseButtonEventInfo.Button);
712 MouseMotionEventInfo mouseMotionEventInfo = eventInfo as MouseMotionEventInfo;
713 if (mouseMotionEventInfo != null)
715 mPacketWriter.Write((short)mouseMotionEventInfo.X);
716 mPacketWriter.Write((short)mouseMotionEventInfo.Y);
723 bool IsLatencyAdjustmentFrame
727 return mNextLatencyAdjustmentFrame == mGame.CurrentFrameNumber;
733 Debug.Assert(IsLatencyAdjustmentFrame);
735 int maxStallCount = 0;
736 int maxAverageOwd = 0;
738 foreach (GamerInfo gamerInfo in GamerArray)
740 if (gamerInfo.StallCount > maxStallCount) maxStallCount = gamerInfo.StallCount;
741 if (gamerInfo.AverageOwd > maxAverageOwd) maxAverageOwd = gamerInfo.AverageOwd;
745 int prevLatency = mLatency;
747 if (maxStallCount > 0)
749 mLatency += maxStallCount;
753 mLatency = (int)(0.6 * (double)(mLatency - maxAverageOwd) + 1.0);
757 if (prevLatency != mLatency) Console.WriteLine("Latency readjusted to " + mLatency);
759 if (mLatency < 1) mLatency = 1;
760 if (mLatency > MaximumLatency) mLatency = MaximumLatency;
762 mNextLatencyAdjustmentFrame = mGame.CurrentFrameNumber + mLatency;
763 mAverageOwd = CurrentAverageOneWayDelay;
765 mLastLocalEvents = mLocalEvents;
766 mLocalEvents = new List<EventInfo>();
771 List<EventInfo> GetEventsFromInput()
773 List<EventInfo> events = new List<EventInfo>();
775 // 1. Find the keyboard differences; written by Peter.
777 KeyboardState keyState = Keyboard.GetState();
779 List<Keys> pressedKeys = new List<Keys>();
780 List<Keys> releasedKeys = new List<Keys>();
782 Keys[] pressedKeysArray = keyState.GetPressedKeys();
783 foreach (Keys k in pressedKeysArray)
785 if (!mLastPressedKeys.Contains(k)) pressedKeys.Add(k);
786 else mLastPressedKeys.Remove(k);
789 releasedKeys = mLastPressedKeys;
791 foreach (Keys key in pressedKeys)
793 events.Add(new KeyboardEventInfo(LocalGamer, mGame.CurrentFrameNumber, key, true));
795 foreach (Keys key in releasedKeys)
797 events.Add(new KeyboardEventInfo(LocalGamer, mGame.CurrentFrameNumber, key, false));
800 // 2. Find the mouse differences.
802 MouseState mouseState = Mouse.GetState();
804 bool leftButtonPressed = mouseState.LeftButton == ButtonState.Pressed;
805 if (leftButtonPressed != mLastLeftButtonPressed)
807 events.Add(new MouseButtonEventInfo(LocalGamer, mGame.CurrentFrameNumber, MouseButton.Left, leftButtonPressed));
810 bool rightButtonPressed = mouseState.LeftButton == ButtonState.Pressed;
811 if (rightButtonPressed != mLastRightButtonPressed)
813 events.Add(new MouseButtonEventInfo(LocalGamer, mGame.CurrentFrameNumber, MouseButton.Right, rightButtonPressed));
816 bool middleButtonPressed = mouseState.LeftButton == ButtonState.Pressed;
817 if (middleButtonPressed != mLastMiddleButtonPressed)
819 events.Add(new MouseButtonEventInfo(LocalGamer, mGame.CurrentFrameNumber, MouseButton.Middle, middleButtonPressed));
822 int mousePositionX = mouseState.X;
823 int mousePositionY = mouseState.Y;
824 if (mousePositionX != mLastMousePositionX || mousePositionY != mLastMousePositionY)
826 events.Add(new MouseMotionEventInfo(LocalGamer, mGame.CurrentFrameNumber, mousePositionX, mousePositionY));
829 // 3. Save the current peripheral state.
831 mLastPressedKeys = new List<Keys>(pressedKeysArray);
832 mLastLeftButtonPressed = leftButtonPressed;
833 mLastRightButtonPressed = rightButtonPressed;
834 mLastMiddleButtonPressed = middleButtonPressed;
835 mLastMousePositionX = mousePositionX;
836 mLastMousePositionY = mousePositionY;
841 void SendLocalEvents()
843 SendLocalEvents((NetworkGamer)null);
846 void SendLocalEvents(List<NetworkGamer> recipicents)
848 foreach (NetworkGamer gamer in recipicents)
850 SendLocalEvents(gamer);
854 void SendLocalEvents(NetworkGamer recipient)
856 List<EventInfo> events = new List<EventInfo>(mLocalEvents);
857 events.AddRange(mLastLocalEvents);
859 WriteEventPacket(events);
861 if (recipient != null)
863 LocalGamer.SendData(mPacketWriter, SendDataOptions.Reliable, recipient);
867 LocalGamer.SendData(mPacketWriter, SendDataOptions.None);
872 bool HaveNeededEvents
876 long currentFrame = mGame.CurrentFrameNumber;
878 foreach (GamerInfo gamerInfo in mGamers.Values)
880 if (gamerInfo.HighestFrameNumber < currentFrame) return false;
889 int index = EventArrayIndex;
891 foreach (GamerInfo gamerInfo in GamerArray)
893 if (gamerInfo.Events[index] == null) continue;
895 foreach (EventInfo eventInfo in gamerInfo.Events[index])
897 KeyboardEventInfo keyboardEventInfo = eventInfo as KeyboardEventInfo;
898 if (keyboardEventInfo != null)
900 mGame.ApplyKeyInput(gamerInfo, keyboardEventInfo.Key, keyboardEventInfo.IsKeyDown);
904 MouseButtonEventInfo mouseButtonEventInfo = eventInfo as MouseButtonEventInfo;
905 if (mouseButtonEventInfo != null)
907 mGame.ApplyMouseButtonInput(gamerInfo, mouseButtonEventInfo.IsButtonDown);
911 MouseMotionEventInfo mouseMotionEventInfo = eventInfo as MouseMotionEventInfo;
912 if (mouseMotionEventInfo != null)
914 mGame.ApplyMouseLocationInput(gamerInfo, mouseMotionEventInfo.X, mouseMotionEventInfo.Y);
919 gamerInfo.Events[index] = null;
924 int CurrentAverageOneWayDelay
928 Debug.Assert(mNetworkSession != null);
930 double numRemoteGamersTwice = 2 * mNetworkSession.RemoteGamers.Count;
931 double averageOwd = 0;
933 foreach (NetworkGamer gamer in mNetworkSession.RemoteGamers)
935 TimeSpan timeSpan = gamer.RoundtripTime;
936 averageOwd += timeSpan.TotalMilliseconds;
939 return (int)(averageOwd / numRemoteGamersTwice / 16.6666);