-using System;\r
+\r
+// Make sure DEBUG is undefined when turning in the project\r
+// or the grader will wonder why it's so laggy.\r
+#undef DEBUG\r
+\r
+using System;\r
using System.Collections.Generic;\r
using System.Linq;\r
using System.Text;\r
using Microsoft.Xna.Framework.Graphics;\r
using Microsoft.Xna.Framework;\r
using Microsoft.Xna.Framework.Input;\r
+using System.Collections;\r
\r
namespace CS_3505_Project_06\r
{\r
+ /// <summary>\r
+ /// A manager class to handle network interactions between peers and\r
+ /// lobby/game switching.\r
+ /// </summary>\r
public class NetworkGame\r
{\r
- NetworkSession mNetworkSession;\r
-\r
- ILobby mLobby;\r
- IDeterministicGame mGame;\r
-\r
- TimeSpan mTargetTimeSpan = new TimeSpan(166666);\r
- public TimeSpan TargetTimeSpan\r
- {\r
- get\r
- {\r
- return mTargetTimeSpan;\r
- }\r
- }\r
-\r
- List<Keys> lastPressedKeys;\r
- bool lastButtonPressed;\r
-\r
- Object[] playerIdentifiers = { "One", "Two", "Three", "Four" }; // Any objects will do, strings are easy to debug.\r
-\r
- // For debugging\r
-\r
- Object activePlayer;\r
- bool paused;\r
- long lastAutoPause;\r
-\r
- public SpriteFont font;\r
-\r
-\r
+ // Public methods and properties\r
+ #region Public Methods\r
+\r
+ /// <summary>\r
+ /// Called when a session has been created or joined using CreateSession() or JoinSession().\r
+ /// </summary>\r
+ /// <param name="session">The new session that was created or joined.</param>\r
+ /// <param name="networkGame">The NetworkGame that joined the session.</param>\r
+ public delegate void JoinedSessionDelegate(NetworkSession session, NetworkGame networkGame);\r
+\r
+ /// <summary>\r
+ /// Called when sessions are found as a result of calling FindSessions().\r
+ /// </summary>\r
+ /// <param name="sessions">A container of the available sessions.</param>\r
+ /// <param name="networkGame">The NetworkGame that searched for the sessions.</param>\r
+ public delegate void FoundSessionsDelegate(AvailableNetworkSessionCollection sessions, NetworkGame networkGame);\r
+\r
+\r
+ /// <summary>\r
+ /// Called when an exception is thrown during an asynchronous operation.\r
+ /// </summary>\r
+ /// <param name="exception">The exception that was thrown.</param>\r
+ /// <param name="networkGame">The NetworkGame that errored.</param>\r
+ public delegate void CaughtErrorDelegate(Exception exception, NetworkGame networkGame);\r
+\r
+ /// <summary>\r
+ /// Get and set the error delegate, called when an exception is thrown during\r
+ /// and asynchronous operation. This will occur if you try to create or join a\r
+ /// session without being logged into a profile.\r
+ /// </summary>\r
+ public CaughtErrorDelegate ErrorDelegate;\r
+\r
+\r
+ /// <summary>\r
+ /// Construct a NetworkGame with a lobby and a game.\r
+ /// </summary>\r
+ /// <param name="lobby">Provides an associated lobby to update and draw.</param>\r
+ /// <param name="game">Provides a game object to be played over the network.</param>\r
public NetworkGame(ILobby lobby, IDeterministicGame game)\r
{\r
Debug.Assert(lobby != null && game != null);\r
\r
mLobby = lobby;\r
mGame = game;\r
-\r
- // Begin: Test harness stuff\r
- lastPressedKeys = new List<Keys>();\r
- activePlayer = playerIdentifiers[0];\r
- paused = false;\r
-\r
- // Reset the game - indicate that player #1 (player 0) owns this instance of the game.\r
-\r
- mGame.ResetGame(playerIdentifiers, playerIdentifiers[0]);\r
}\r
\r
\r
- LocalNetworkGamer LocalGamer\r
+ /// <summary>\r
+ /// Get the Gamer object for the local player.\r
+ /// </summary>\r
+ public LocalNetworkGamer LocalGamer\r
{\r
get\r
{\r
+ // TODO: Is this the correct way to get the single local gamer?\r
return mNetworkSession.LocalGamers[0];\r
}\r
}\r
\r
+ /// <summary>\r
+ /// Get all the gamers associated with the active network session.\r
+ /// </summary>\r
+ public GamerCollection<NetworkGamer> NetworkGamers\r
+ {\r
+ get\r
+ {\r
+ return mNetworkSession.AllGamers;\r
+ }\r
+ }\r
+\r
\r
- NetworkSession CreateSession()\r
+ /// <summary>\r
+ /// Begin a new network session with the local gamer as the host. You must not\r
+ /// call this method or use JoinSession without first using LeaveSession.\r
+ /// </summary>\r
+ /// <param name="callback">The delegate/method to call when the session is created.</param>\r
+ public void CreateSession(JoinedSessionDelegate callback)\r
{\r
- return CreateSession(mGame.MaximumSupportedPlayers);\r
+ CreateSession(mGame.MaximumSupportedPlayers, callback);\r
}\r
\r
- public NetworkSession CreateSession(int maxGamers)\r
+ /// <summary>\r
+ /// Begin a new network session with the local gamer as the host. You must not\r
+ /// call this method or use JoinSession without first using LeaveSession.\r
+ /// </summary>\r
+ /// <param name="maxGamers">Provide the maximum number of players allowed to connect.</param>\r
+ /// <param name="callback">The delegate/method to call when the session is created.</param>\r
+ public void CreateSession(int maxGamers, JoinedSessionDelegate callback)\r
{\r
- Debug.Assert(mNetworkSession == null); \r
+ Debug.Assert(mNetworkSession == null);\r
\r
- mNetworkSession = NetworkSession.Create(NetworkSessionType.SystemLink, 1, maxGamers);\r
- mNetworkSession.AllowHostMigration = true;\r
- mNetworkSession.AllowJoinInProgress = false;\r
+ mJoinedSessionDelegate = callback;\r
+ NetworkSession.BeginCreate(NetworkSessionType.SystemLink, 1, maxGamers, CreateSessionEnd, null);\r
+ }\r
+ void CreateSessionEnd(IAsyncResult result)\r
+ {\r
+ Debug.Assert(mNetworkSession == null);\r
\r
- return mNetworkSession;\r
+ try\r
+ {\r
+ mNetworkSession = NetworkSession.EndCreate(result);\r
+ mNetworkSession.AllowHostMigration = true;\r
+ mNetworkSession.AllowJoinInProgress = false;\r
+ mNetworkSession.GameStarted += new EventHandler<GameStartedEventArgs>(GameStartedEvent);\r
+ }\r
+ catch (Exception e)\r
+ {\r
+ if (ErrorDelegate != null) ErrorDelegate(e, this);\r
+ return;\r
+ }\r
+ mJoinedSessionDelegate(mNetworkSession, this);\r
+ mJoinedSessionDelegate = null;\r
+ }\r
+ void GameStartedEvent(object sender, GameStartedEventArgs e)\r
+ {\r
+ Reset();\r
+ }\r
+\r
+ /// <summary>\r
+ /// Determine whether or not the network game object is associated with any network session.\r
+ /// </summary>\r
+ /// <returns>True if there exists a NetworkSession; false otherwise.</returns>\r
+ public bool HasActiveSession\r
+ {\r
+ get\r
+ {\r
+ return mNetworkSession != null;\r
+ }\r
}\r
\r
\r
- public AvailableNetworkSessionCollection FindSessions()\r
+ /// <summary>\r
+ /// Find available sessions to join. You should not already be in a session when\r
+ /// calling this method; call LeaveSession first.\r
+ /// </summary>\r
+ /// <param name="callback">The delegate/method to call when the search finishes.</param>\r
+ public void FindSessions(FoundSessionsDelegate callback)\r
{\r
- return NetworkSession.Find(NetworkSessionType.SystemLink, 1, new NetworkSessionProperties());\r
+ Debug.Assert(mNetworkSession == null);\r
+\r
+ mFoundSessionsDelegate = callback;\r
+ NetworkSession.BeginFind(NetworkSessionType.SystemLink, 1, null, new AsyncCallback(FindSessionsEnd), null);\r
+ }\r
+ void FindSessionsEnd(IAsyncResult result)\r
+ {\r
+ AvailableNetworkSessionCollection sessions;\r
+ try\r
+ {\r
+ sessions = NetworkSession.EndFind(result);\r
+ }\r
+ catch (Exception e)\r
+ {\r
+ if (ErrorDelegate != null) ErrorDelegate(e, this);\r
+ return;\r
+ }\r
+ mFoundSessionsDelegate(sessions, this);\r
+ mFoundSessionsDelegate = null;\r
}\r
\r
- public NetworkSession JoinSession(AvailableNetworkSession availableSession)\r
+ /// <summary>\r
+ /// Join a network session found using FindSessions(). This is for joining a game that\r
+ /// somebody else has already started hosting. You must not already be in a session.\r
+ /// </summary>\r
+ /// <param name="availableSession">Pass the session object to try to join.</param>\r
+ /// <param name="callback">The delegate/method to call when the search finishes.</param>\r
+ public void JoinSession(AvailableNetworkSession availableSession, JoinedSessionDelegate callback)\r
{\r
Debug.Assert(mNetworkSession == null);\r
\r
- mNetworkSession = NetworkSession.Join(availableSession);\r
+ mJoinedSessionDelegate = callback;\r
+ NetworkSession.BeginJoin(availableSession, JoinSessionEnd, null);\r
+ }\r
+ void JoinSessionEnd(IAsyncResult result)\r
+ {\r
+ Debug.Assert(mNetworkSession == null);\r
\r
- return mNetworkSession;\r
+ try\r
+ {\r
+ mNetworkSession = NetworkSession.EndJoin(result);\r
+ mNetworkSession.GameStarted += new EventHandler<GameStartedEventArgs>(GameStartedEvent);\r
+ }\r
+ catch (Exception e)\r
+ {\r
+ if (ErrorDelegate != null) ErrorDelegate(e, this);\r
+ return;\r
+ }\r
+ mJoinedSessionDelegate(mNetworkSession, this);\r
+ mJoinedSessionDelegate = null;\r
}\r
\r
\r
+ /// <summary>\r
+ /// Leave and dispose of any currently associated network session. You will find yourself\r
+ /// back in the lobby. You must already be in a session to leave it.\r
+ /// </summary>\r
public void LeaveSession()\r
{\r
Debug.Assert(mNetworkSession != null);\r
}\r
\r
\r
+ /// <summary>\r
+ /// Set up the network session to simulate 200ms latency and 10% packet loss.\r
+ /// </summary>\r
public void SimulateBadNetwork()\r
{\r
Debug.Assert(mNetworkSession != null);\r
}\r
\r
\r
+ /// <summary>\r
+ /// Indicate that the game should begin (moving players from the lobby to the game).\r
+ /// You must call CreateSession() before calling this.\r
+ /// </summary>\r
+ public void StartGame()\r
+ {\r
+ Debug.Assert(mNetworkSession != null && mNetworkSession.IsHost &&\r
+ mNetworkSession.AllGamers.Count >= mGame.MinimumSupportedPlayers &&\r
+ mNetworkSession.IsEveryoneReady);\r
+\r
+ ForceStartGame();\r
+ }\r
+\r
+ /// <summary>\r
+ /// Indicate that the game should begin. This is like StartGame() without the sanity\r
+ /// checks. Use this for debugging.\r
+ /// </summary>\r
+ public void ForceStartGame()\r
+ {\r
+ mNetworkSession.StartGame();\r
+ mNetworkSession.ResetReady();\r
+ }\r
+\r
+\r
+ /// <summary>\r
+ /// Manages the network session and allows either the lobby or game to update.\r
+ /// </summary>\r
+ /// <param name="gameTime">Pass the time away.</param>\r
public void Update(GameTime gameTime)\r
{\r
if (mNetworkSession == null)\r
else\r
{\r
mNetworkSession.Update();\r
+ HandleIncomingPackets();\r
\r
if (mNetworkSession.SessionState == NetworkSessionState.Lobby)\r
{\r
- if (mNetworkSession.IsHost &&\r
- mNetworkSession.AllGamers.Count >= mGame.MinimumSupportedPlayers &&\r
- mNetworkSession.IsEveryoneReady)\r
+ mLobby.Update(gameTime, this);\r
+ }\r
+ else if (mNetworkSession.SessionState == NetworkSessionState.Playing)\r
+ {\r
+ if (mGame.IsTerminated(LocalGamerInfo))\r
{\r
- mNetworkSession.StartGame();\r
- mNetworkSession.ResetReady();\r
+ LeaveSession();\r
+ return;\r
}\r
- else\r
+ else if (mGame.IsGameOver(LocalGamerInfo))\r
+ {\r
+ ApplyEvents(LocalGamerInfo, GetEventsFromInput());\r
+ mGame.Update(mTargetTimeSpan);\r
+ return;\r
+ }\r
+\r
+ if (HaveNeededEvents)\r
{\r
- mLobby.Update(gameTime, this);\r
+ if (IsLatencyAdjustmentFrame)\r
+ {\r
+ AdjustLatency();\r
+ mLastStallCount = mStallCount;\r
+ mStallCount = 0;\r
+ }\r
+ mLocalEvents.AddRange(GetEventsFromInput());\r
+ SendLocalEvents();\r
+ ApplyEvents();\r
+\r
+#if DEBUG\r
+ Console.WriteLine("HASH: " + mGame.CurrentFrameNumber + "\t" + mGame.CurrentChecksum);\r
+#endif\r
+\r
+ mGame.Update(mTargetTimeSpan);\r
}\r
+ else // Stall!\r
+ {\r
+ mStallCount++;\r
+\r
+ // Send a reliable event packet to each stalled gamer.\r
+ if (mStallCount == 1)\r
+ {\r
+#if DEBUG\r
+ Console.WriteLine("STAL: ====");\r
+#endif\r
+\r
+ foreach (GamerInfo gamerInfo in GamerArray)\r
+ {\r
+ if (gamerInfo.HighestFrameNumber < mGame.CurrentFrameNumber)\r
+ {\r
+ SendLocalEvents(gamerInfo.Gamer);\r
+ }\r
+ }\r
+ }\r
+ else if (mStallCount > 600)\r
+ {\r
+ Console.WriteLine("One or more players have stalled excessively. Leaving session...");\r
+ LeaveSession();\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /// <summary>\r
+ /// Allows either the lobby or the game to draw, depending on the state\r
+ /// of the network connection and whether or not a game is in progress.\r
+ /// </summary>\r
+ /// <param name="gameTime">Pass the time away.</param>\r
+ /// <param name="spriteBatch">The sprite batch.</param>\r
+ public void Draw(GameTime gameTime, SpriteBatch spriteBatch)\r
+ {\r
+ if (mNetworkSession == null)\r
+ {\r
+ mLobby.Draw(spriteBatch);\r
+ }\r
+ else\r
+ {\r
+ if (mNetworkSession.SessionState == NetworkSessionState.Lobby)\r
+ {\r
+ mLobby.Draw(spriteBatch);\r
}\r
else if (mNetworkSession.SessionState == NetworkSessionState.Playing)\r
{\r
- // TODO: in-game update stuff\r
- UpdateTestHarness(gameTime);\r
+ mGame.Draw(spriteBatch);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /// <summary>\r
+ /// Get the chat messages that have been received since the last time this\r
+ /// method was called.\r
+ /// </summary>\r
+ /// <returns>List container of the chat messages.</returns>\r
+ public List<ChatInfo> ReceiveChats()\r
+ {\r
+ List<ChatInfo> chats = mChatPackets;\r
+ mChatPackets = new List<ChatInfo>();\r
+ return chats;\r
+ }\r
+\r
+ /// <summary>\r
+ /// Send a chat message to all gamers in the session. You should already be\r
+ /// in a session before calling this method.\r
+ /// </summary>\r
+ /// <param name="message">The text of the message.</param>\r
+ public void SendChat(String message)\r
+ {\r
+ WriteChatPacket(message);\r
+ LocalGamer.SendData(mPacketWriter, SendDataOptions.ReliableInOrder);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Send a chat message to a specific gamer in the session. You should already\r
+ /// be in a session before calling this method.\r
+ /// </summary>\r
+ /// <param name="message">The text of the message.</param>\r
+ /// <param name="recipient">The gamer to receive the message.</param>\r
+ public void SendChat(String message, NetworkGamer recipient)\r
+ {\r
+ Debug.Assert(recipient != null && !recipient.IsDisposed);\r
+\r
+ WriteChatPacket(message);\r
+ LocalGamer.SendData(mPacketWriter, SendDataOptions.ReliableInOrder, recipient);\r
+ }\r
+\r
+ #endregion\r
+\r
+\r
+ // Private class variable members\r
+ #region Instance Variables\r
+\r
+ NetworkSession mNetworkSession;\r
+ PacketReader mPacketReader = new PacketReader();\r
+ PacketWriter mPacketWriter = new PacketWriter();\r
+\r
+ JoinedSessionDelegate mJoinedSessionDelegate;\r
+ FoundSessionsDelegate mFoundSessionsDelegate;\r
+\r
+ ILobby mLobby;\r
+ IDeterministicGame mGame;\r
+\r
+ List<ChatInfo> mChatPackets = new List<ChatInfo>();\r
+\r
+ List<EventInfo> mLocalEvents = new List<EventInfo>();\r
+ List<EventInfo> mLastLocalEvents = new List<EventInfo>();\r
+\r
+ List<Keys> mLastPressedKeys = new List<Keys>();\r
+ bool mLastLeftButtonPressed;\r
+ bool mLastRightButtonPressed;\r
+ bool mLastMiddleButtonPressed;\r
+ int mLastMousePositionX;\r
+ int mLastMousePositionY;\r
+\r
+ int mLatency;\r
+ long mHighestFrameNumber;\r
+ long mNextLatencyAdjustmentFrame;\r
+ int mStallCount;\r
+ int mLastStallCount;\r
+ int mAverageOwd;\r
+\r
+#if DEBUG\r
+ bool mDontSendEvents;\r
+#endif\r
+\r
+ TimeSpan mTargetTimeSpan = new TimeSpan(166666);\r
+ public TimeSpan TargetTimeSpan\r
+ {\r
+ get\r
+ {\r
+ return mTargetTimeSpan;\r
+ }\r
+ }\r
+\r
+ Dictionary<byte, GamerInfo> mGamers;\r
+ GamerInfo[] GamerArray\r
+ {\r
+ get\r
+ {\r
+ GamerInfo[] gamerList = mGamers.Values.ToArray();\r
+ Array.Sort(gamerList, delegate(GamerInfo a, GamerInfo b)\r
+ {\r
+ return a.Gamer.Id.CompareTo(b.Gamer.Id);\r
+ });\r
+ return gamerList;\r
+ }\r
+ }\r
+ GamerInfo LocalGamerInfo\r
+ {\r
+ get\r
+ {\r
+ return mGamers[LocalGamer.Id];\r
+ }\r
+ }\r
+\r
+ #endregion\r
+\r
+\r
+ // Private types for the implementation of the network protocol\r
+ #region Private Types\r
+ \r
+ enum PacketType\r
+ {\r
+ Chat = 1,\r
+ Event = 2\r
+ }\r
+\r
+ enum EventType\r
+ {\r
+ KeyDown = 1,\r
+ KeyUp = 2,\r
+ MouseDown = 3,\r
+ MouseUp = 4,\r
+ MouseMove = 5\r
+ }\r
+\r
+ enum MouseButton\r
+ {\r
+ Left = 1,\r
+ Right = 2,\r
+ Middle = 3\r
+ }\r
+\r
+ abstract class EventInfo\r
+ {\r
+ public NetworkGamer Gamer;\r
+ public long FrameOfApplication;\r
+\r
+ public EventInfo(NetworkGamer gamer, long frameNumber)\r
+ {\r
+ Gamer = gamer;\r
+ FrameOfApplication = frameNumber;\r
+ }\r
+\r
+ public abstract EventType Id\r
+ {\r
+ get;\r
+ }\r
+ }\r
+\r
+ class KeyboardEventInfo : EventInfo\r
+ {\r
+ public Keys Key;\r
+ public bool IsKeyDown;\r
+\r
+ public KeyboardEventInfo(NetworkGamer gamer, long frameNumber, Keys key, bool isDown)\r
+ : base(gamer, frameNumber)\r
+ {\r
+ Key = key;\r
+ IsKeyDown = isDown;\r
+ }\r
+\r
+ public override EventType Id\r
+ {\r
+ get { return IsKeyDown ? EventType.KeyDown : EventType.KeyUp; }\r
+ }\r
+ }\r
+\r
+ class MouseButtonEventInfo : EventInfo\r
+ {\r
+ public MouseButton Button;\r
+ public bool IsButtonDown;\r
\r
- mGame.Update(mTargetTimeSpan);\r
+ public MouseButtonEventInfo(NetworkGamer gamer, long frameNumber, MouseButton button, bool isDown)\r
+ : base(gamer, frameNumber)\r
+ {\r
+ Button = button;\r
+ IsButtonDown = isDown;\r
+ }\r
+\r
+ public override EventType Id\r
+ {\r
+ get { return IsButtonDown ? EventType.MouseDown : EventType.MouseUp; }\r
+ }\r
+ }\r
+\r
+ class MouseMotionEventInfo : EventInfo\r
+ {\r
+ public int X;\r
+ public int Y;\r
+\r
+ public MouseMotionEventInfo(NetworkGamer gamer, long frameNumber, int x, int y)\r
+ : base(gamer, frameNumber)\r
+ {\r
+ X = x;\r
+ Y = y;\r
+ }\r
+\r
+ public override EventType Id\r
+ {\r
+ get { return EventType.MouseMove; }\r
+ }\r
+ }\r
+\r
+ class GamerInfo\r
+ {\r
+ public NetworkGamer Gamer;\r
+ public long HighestFrameNumber = 0;\r
+ public int StallCount = 0;\r
+ public int AverageOwd = 0;\r
+ public int NextStallCount = 0;\r
+ public int NextAverageOwd = 0;\r
+ public bool IsWaitedOn = false;\r
+ public List<EventInfo>[] Events = new List<EventInfo>[MaximumLatency];\r
+\r
+ public GamerInfo(NetworkGamer gamer)\r
+ {\r
+ Gamer = gamer;\r
+ }\r
+ }\r
+\r
+ const int MaximumLatency = 120;\r
+ const int StallTimeout = 900;\r
+\r
+ #endregion\r
+\r
+\r
+ // Private implementation methods of the network protocol\r
+ #region Private Implementation Methods\r
+\r
+ /// <summary>\r
+ /// Reinitialize the private variables in preparation for a new game to start.\r
+ /// </summary>\r
+ void Reset()\r
+ {\r
+ mLatency = 1;\r
+ mHighestFrameNumber = 0;\r
+ mNextLatencyAdjustmentFrame = 1;\r
+ mStallCount = 0;\r
+ mLastStallCount = 0;\r
+ mAverageOwd = CurrentAverageOneWayDelay;\r
+\r
+ mGamers = new Dictionary<byte, GamerInfo>();\r
+ foreach (NetworkGamer gamer in NetworkGamers)\r
+ {\r
+ mGamers.Add(gamer.Id, new GamerInfo(gamer));\r
+ }\r
+\r
+ mGame.ResetGame(GamerArray, LocalGamerInfo);\r
+ }\r
+\r
+\r
+ void HandleIncomingPackets()\r
+ {\r
+ LocalNetworkGamer localGamer = LocalGamer;\r
+\r
+ while (localGamer.IsDataAvailable)\r
+ {\r
+ NetworkGamer sender;\r
+\r
+ localGamer.ReceiveData(mPacketReader, out sender);\r
+\r
+ PacketType packetId = (PacketType)mPacketReader.ReadByte();\r
+ switch (packetId)\r
+ {\r
+ case PacketType.Chat:\r
+\r
+ short messageLength = mPacketReader.ReadInt16();\r
+ char[] message = mPacketReader.ReadChars(messageLength);\r
+\r
+ ChatInfo chatPacket = new ChatInfo(sender, new String(message));\r
+ mChatPackets.Add(chatPacket);\r
+ break;\r
+\r
+ case PacketType.Event:\r
+\r
+ GamerInfo senderInfo = mGamers[sender.Id];\r
+\r
+ int stallCount = mPacketReader.ReadInt16();\r
+ int averageOwd = mPacketReader.ReadInt16();\r
+ int frameNumber = mPacketReader.ReadInt32();\r
+ int numEvents = mPacketReader.ReadByte();\r
+\r
+ if (frameNumber <= mNextLatencyAdjustmentFrame)\r
+ {\r
+ senderInfo.StallCount = stallCount;\r
+ senderInfo.AverageOwd = averageOwd;\r
+ }\r
+ else\r
+ {\r
+ senderInfo.NextStallCount = stallCount;\r
+ senderInfo.NextAverageOwd = averageOwd;\r
+ }\r
+\r
+ if (frameNumber <= senderInfo.HighestFrameNumber)\r
+ {\r
+#if DEBUG\r
+ Console.WriteLine("SKP" + (char)sender.Id + ": " + mGame.CurrentFrameNumber + "\t" + frameNumber + "\t<=\t" + senderInfo.HighestFrameNumber + "\t#" + numEvents);\r
+#endif\r
+\r
+ // we know about all these events, so don't bother reading them\r
+ break;\r
+ }\r
+\r
+#if DEBUG\r
+ Console.WriteLine(" GOT" + (char)sender.Id + ": " + mGame.CurrentFrameNumber + "\t" + frameNumber + "\t>\t" + senderInfo.HighestFrameNumber + "\t#" + numEvents);\r
+#endif\r
+\r
+ for (int i = 0; i < numEvents; i++)\r
+ {\r
+ EventInfo eventInfo = ReadEvent(mPacketReader, sender);\r
+ \r
+ if (eventInfo != null && eventInfo.FrameOfApplication > senderInfo.HighestFrameNumber)\r
+ {\r
+ int index = GetEventArrayIndexForFrame(eventInfo.FrameOfApplication);\r
+ if (senderInfo.Events[index] == null) senderInfo.Events[index] = new List<EventInfo>();\r
+ senderInfo.Events[index].Add(eventInfo);\r
+ }\r
+ }\r
+\r
+ senderInfo.HighestFrameNumber = frameNumber;\r
+ break;\r
+\r
+ default:\r
+\r
+ Console.WriteLine("Received unknown packet type: " + (int)packetId);\r
+ break;\r
}\r
}\r
}\r
\r
- public void Draw(GameTime gameTime, SpriteBatch spriteBatch)\r
+\r
+ int CurrentEventArrayIndex\r
{\r
- mLobby.Draw(spriteBatch);\r
- DrawTestHarness(gameTime, spriteBatch);\r
+ get { return GetEventArrayIndexForFrame(mGame.CurrentFrameNumber); }\r
}\r
\r
+ int GetEventArrayIndexForFrame(long frame)\r
+ {\r
+ return (int)(frame % MaximumLatency);\r
+ }\r
\r
+ EventInfo ReadEvent(PacketReader packetReader, NetworkGamer sender)\r
+ {\r
+ EventType eventId = (EventType)packetReader.ReadByte();\r
+ long frameNumber = packetReader.ReadInt32();\r
\r
+ switch (eventId)\r
+ {\r
+ case EventType.KeyDown:\r
+\r
+ Keys keyCode1 = (Keys)packetReader.ReadInt32();\r
+ return new KeyboardEventInfo(sender, frameNumber, keyCode1, true);\r
+\r
+ case EventType.KeyUp:\r
+\r
+ Keys keyCode2 = (Keys)packetReader.ReadInt32();\r
+ return new KeyboardEventInfo(sender, frameNumber, keyCode2, false);\r
+\r
+ case EventType.MouseDown:\r
+\r
+ MouseButton buttonId1 = (MouseButton)packetReader.ReadByte();\r
+ return new MouseButtonEventInfo(sender, frameNumber, buttonId1, true);\r
+\r
+ case EventType.MouseUp:\r
+\r
+ MouseButton buttonId2 = (MouseButton)packetReader.ReadByte();\r
+ return new MouseButtonEventInfo(sender, frameNumber, buttonId2, false);\r
+\r
+ case EventType.MouseMove:\r
+\r
+ short x = packetReader.ReadInt16();\r
+ short y = packetReader.ReadInt16();\r
+ return new MouseMotionEventInfo(sender, frameNumber, x, y);\r
+\r
+ default:\r
\r
- void UpdateTestHarness(GameTime gameTime)\r
+ Console.WriteLine("Received unknown event type: " + (int)eventId);\r
+ return null;\r
+ }\r
+ }\r
+\r
+\r
+ void WriteChatPacket(String message)\r
+ {\r
+ mPacketWriter.Write((byte)PacketType.Chat);\r
+ mPacketWriter.Write((short)message.Length);\r
+ mPacketWriter.Write(message.ToCharArray());\r
+ }\r
+\r
+ void WriteEventPacket(List<EventInfo> events, long highestFrameNumber)\r
{\r
- // Get user's input state.\r
+ mPacketWriter.Write((byte)PacketType.Event);\r
+ mPacketWriter.Write((short)mLastStallCount);\r
+ mPacketWriter.Write((short)mAverageOwd);\r
+ mPacketWriter.Write((int)highestFrameNumber);\r
+ mPacketWriter.Write((byte)events.Count);\r
\r
- KeyboardState keyState = Keyboard.GetState();\r
- MouseState mouseState = Mouse.GetState();\r
+ foreach (EventInfo eventInfo in events)\r
+ {\r
+ mPacketWriter.Write((byte)eventInfo.Id);\r
+ mPacketWriter.Write((int)eventInfo.FrameOfApplication);\r
+\r
+ KeyboardEventInfo keyboardEventInfo = eventInfo as KeyboardEventInfo;\r
+ if (keyboardEventInfo != null)\r
+ {\r
+ mPacketWriter.Write((int)keyboardEventInfo.Key);\r
+ continue;\r
+ }\r
+\r
+ MouseButtonEventInfo mouseButtonEventInfo = eventInfo as MouseButtonEventInfo;\r
+ if (mouseButtonEventInfo != null)\r
+ {\r
+ mPacketWriter.Write((byte)mouseButtonEventInfo.Button);\r
+ continue;\r
+ }\r
+\r
+ MouseMotionEventInfo mouseMotionEventInfo = eventInfo as MouseMotionEventInfo;\r
+ if (mouseMotionEventInfo != null)\r
+ {\r
+ mPacketWriter.Write((short)mouseMotionEventInfo.X);\r
+ mPacketWriter.Write((short)mouseMotionEventInfo.Y);\r
+ continue;\r
+ }\r
+ }\r
+ }\r
\r
- // Make a list of the keys pressed or released this frame.\r
+\r
+ bool IsLatencyAdjustmentFrame\r
+ {\r
+ get\r
+ {\r
+ return mNextLatencyAdjustmentFrame == mGame.CurrentFrameNumber;\r
+ }\r
+ }\r
+\r
+ void AdjustLatency()\r
+ {\r
+ Debug.Assert(IsLatencyAdjustmentFrame);\r
+\r
+#if DEBUG\r
+ if (mStallCount > 0)\r
+ {\r
+ Console.WriteLine("STL#: " + mGame.CurrentFrameNumber + "\t" + mStallCount);\r
+ }\r
+#endif\r
+\r
+ int maxStallCount = 0;\r
+ int maxAverageOwd = 0;\r
+\r
+ foreach (GamerInfo gamerInfo in GamerArray)\r
+ {\r
+ if (gamerInfo.StallCount > maxStallCount) maxStallCount = gamerInfo.StallCount;\r
+ if (gamerInfo.AverageOwd > maxAverageOwd) maxAverageOwd = gamerInfo.AverageOwd;\r
+\r
+ gamerInfo.StallCount = gamerInfo.NextStallCount;\r
+ gamerInfo.AverageOwd = gamerInfo.NextAverageOwd;\r
+ }\r
+\r
+#if DEBUG\r
+ int prevLatency = mLatency;\r
+#endif\r
+\r
+ if (maxStallCount > 0)\r
+ {\r
+ mLatency += maxStallCount;\r
+ }\r
+ else\r
+ {\r
+ mLatency -= (int)(0.6 * (double)(mLatency - maxAverageOwd) + 1.0);\r
+ }\r
+\r
+ if (mLatency < 1) mLatency = 1;\r
+ if (mLatency > MaximumLatency) mLatency = MaximumLatency;\r
+\r
+#if DEBUG\r
+ if (prevLatency != mLatency) Console.WriteLine("NLAG: " + mLatency);\r
+#endif\r
+\r
+ mNextLatencyAdjustmentFrame = mGame.CurrentFrameNumber + mLatency;\r
+ mAverageOwd = CurrentAverageOneWayDelay;\r
+\r
+ mLastLocalEvents = mLocalEvents;\r
+ mLocalEvents = new List<EventInfo>();\r
+ }\r
+\r
+\r
+\r
+ List<EventInfo> GetEventsFromInput()\r
+ {\r
+ List<EventInfo> events = new List<EventInfo>();\r
+\r
+ long frameOfApplication = mGame.CurrentFrameNumber + mLatency;\r
+ if (frameOfApplication <= mHighestFrameNumber) return events;\r
+ else mHighestFrameNumber = frameOfApplication;\r
+\r
+ // 1. Find the keyboard differences; written by Peter.\r
+\r
+ KeyboardState keyState = Keyboard.GetState();\r
\r
List<Keys> pressedKeys = new List<Keys>();\r
List<Keys> releasedKeys = new List<Keys>();\r
\r
Keys[] pressedKeysArray = keyState.GetPressedKeys();\r
foreach (Keys k in pressedKeysArray)\r
- if (!lastPressedKeys.Contains(k))\r
- pressedKeys.Add(k);\r
- else\r
- lastPressedKeys.Remove(k);\r
+ {\r
+ if (!mLastPressedKeys.Contains(k)) pressedKeys.Add(k);\r
+ else mLastPressedKeys.Remove(k);\r
+ }\r
\r
- releasedKeys = lastPressedKeys;\r
- lastPressedKeys = new List<Keys>(pressedKeysArray);\r
+ releasedKeys = mLastPressedKeys;\r
\r
- // Get mouse button state.\r
+ foreach (Keys key in pressedKeys)\r
+ {\r
+ events.Add(new KeyboardEventInfo(LocalGamer, frameOfApplication, key, true));\r
+ }\r
+ foreach (Keys key in releasedKeys)\r
+ {\r
+ events.Add(new KeyboardEventInfo(LocalGamer, frameOfApplication, key, false));\r
+ }\r
\r
- bool buttonPressed = mouseState.LeftButton == ButtonState.Pressed;\r
+#if DEBUG\r
+ if (pressedKeys.Contains(Keys.Escape)) mDontSendEvents = true;\r
+ if (releasedKeys.Contains(Keys.Escape)) mDontSendEvents = false;\r
+#endif\r
\r
- /***** Begining of game logic. *****/\r
+ // 2. Find the mouse differences.\r
\r
- // Debug - allow user on this machine to direct input to any player's state in the game.\r
+ MouseState mouseState = Mouse.GetState();\r
\r
- if (pressedKeys.Contains(Keys.F1)) activePlayer = playerIdentifiers[0];\r
- if (pressedKeys.Contains(Keys.F2)) activePlayer = playerIdentifiers[1];\r
- if (pressedKeys.Contains(Keys.F3)) activePlayer = playerIdentifiers[2];\r
- if (pressedKeys.Contains(Keys.F4)) activePlayer = playerIdentifiers[3];\r
+ bool leftButtonPressed = mouseState.LeftButton == ButtonState.Pressed;\r
+ if (leftButtonPressed != mLastLeftButtonPressed)\r
+ {\r
+ events.Add(new MouseButtonEventInfo(LocalGamer, frameOfApplication, MouseButton.Left, leftButtonPressed));\r
+ }\r
+\r
+ bool rightButtonPressed = mouseState.RightButton == ButtonState.Pressed;\r
+ if (rightButtonPressed != mLastRightButtonPressed)\r
+ {\r
+ events.Add(new MouseButtonEventInfo(LocalGamer, frameOfApplication, MouseButton.Right, rightButtonPressed));\r
+ }\r
\r
- // Debug - allow user on this machine to pause/resume game state advances.\r
+ bool middleButtonPressed = mouseState.MiddleButton == ButtonState.Pressed;\r
+ if (middleButtonPressed != mLastMiddleButtonPressed)\r
+ {\r
+ events.Add(new MouseButtonEventInfo(LocalGamer, frameOfApplication, MouseButton.Middle, middleButtonPressed));\r
+ }\r
\r
- if (pressedKeys.Contains(Keys.F12) ||\r
- pressedKeys.Contains(Keys.P) && (keyState.IsKeyDown(Keys.LeftControl) || keyState.IsKeyDown(Keys.RightControl)))\r
+ int mousePositionX = mouseState.X;\r
+ int mousePositionY = mouseState.Y;\r
+ if (mousePositionX != mLastMousePositionX || mousePositionY != mLastMousePositionY)\r
{\r
- paused = !paused;\r
- return; // Don't update on pause start or stop\r
+ events.Add(new MouseMotionEventInfo(LocalGamer, frameOfApplication, mousePositionX, mousePositionY));\r
}\r
\r
- // Debug - automatically pause every 1000 frames.\r
+ // 3. Save the current peripheral state.\r
+\r
+ mLastPressedKeys = new List<Keys>(pressedKeysArray);\r
+ mLastLeftButtonPressed = leftButtonPressed;\r
+ mLastRightButtonPressed = rightButtonPressed;\r
+ mLastMiddleButtonPressed = middleButtonPressed;\r
+ mLastMousePositionX = mousePositionX;\r
+ mLastMousePositionY = mousePositionY;\r
+\r
+ return events;\r
+ }\r
+\r
+ void SendLocalEvents()\r
+ {\r
+ SendLocalEvents((NetworkGamer)null);\r
+ }\r
\r
- if (mGame.CurrentFrameNumber % 1000 == 0 && mGame.CurrentFrameNumber != lastAutoPause)\r
+ void SendLocalEvents(List<NetworkGamer> recipicents)\r
+ {\r
+ foreach (NetworkGamer gamer in recipicents)\r
{\r
- paused = true;\r
- lastAutoPause = mGame.CurrentFrameNumber;\r
+ SendLocalEvents(gamer);\r
}\r
+ }\r
\r
+ void SendLocalEvents(NetworkGamer recipient)\r
+ {\r
+#if DEBUG\r
+ if (mDontSendEvents) return;\r
+#endif\r
\r
- //if (pressedKeys.Contains(Keys.Escape))\r
- // this.Exit();\r
+ List<EventInfo> events = new List<EventInfo>(mLocalEvents);\r
+ events.AddRange(mLastLocalEvents);\r
\r
- // Game update\r
+ if (recipient != null && !recipient.IsDisposed)\r
+ {\r
+ // if there is a recipient, we are resending old events\r
+ WriteEventPacket(events, mGame.CurrentFrameNumber - 1);\r
+ LocalGamer.SendData(mPacketWriter, SendDataOptions.Reliable, recipient);\r
+ }\r
+ else\r
+ {\r
+ WriteEventPacket(events, mGame.CurrentFrameNumber + mLatency);\r
+ LocalGamer.SendData(mPacketWriter, SendDataOptions.None);\r
+ }\r
+ }\r
\r
- // Direct inputs to the game engine - only report changes.\r
\r
- foreach (Keys k in pressedKeys)\r
- mGame.ApplyKeyInput(activePlayer, k, true);\r
+ bool HaveNeededEvents\r
+ {\r
+ get\r
+ {\r
+ long currentFrame = mGame.CurrentFrameNumber;\r
\r
- foreach (Keys k in releasedKeys)\r
- mGame.ApplyKeyInput(activePlayer, k, false);\r
+ foreach (GamerInfo gamerInfo in mGamers.Values)\r
+ {\r
+ if (mGame.IsGameOver(gamerInfo)) continue;\r
+ if (gamerInfo.HighestFrameNumber < currentFrame) return false;\r
+ }\r
\r
- mGame.ApplyMouseLocationInput(activePlayer, mouseState.X, mouseState.Y);\r
+ return true;\r
+ }\r
+ }\r
\r
- if (lastButtonPressed != buttonPressed)\r
- mGame.ApplyMouseButtonInput(activePlayer, buttonPressed);\r
+ void ApplyEvents()\r
+ {\r
+ int index = CurrentEventArrayIndex;\r
\r
- lastButtonPressed = buttonPressed;\r
+ foreach (GamerInfo gamerInfo in GamerArray)\r
+ {\r
+ if (gamerInfo.Events[index] == null) continue;\r
+ ApplyEvents(gamerInfo, gamerInfo.Events[index]);\r
+ gamerInfo.Events[index] = null;\r
+ }\r
+ }\r
\r
- if (!paused)\r
+ void ApplyEvents(GamerInfo gamerInfo, List<EventInfo> events)\r
+ {\r
+ foreach (EventInfo eventInfo in events)\r
{\r
- // Advance the game engine.\r
+ KeyboardEventInfo keyboardEventInfo = eventInfo as KeyboardEventInfo;\r
+ if (keyboardEventInfo != null)\r
+ {\r
+#if DEBUG\r
+ Console.WriteLine(" KEY: " + keyboardEventInfo.FrameOfApplication + "\t" + keyboardEventInfo.Key + "," + keyboardEventInfo.IsKeyDown);\r
+#endif\r
+\r
+ mGame.ApplyKeyInput(gamerInfo, keyboardEventInfo.Key, keyboardEventInfo.IsKeyDown);\r
+ continue;\r
+ }\r
+\r
+ MouseButtonEventInfo mouseButtonEventInfo = eventInfo as MouseButtonEventInfo;\r
+ if (mouseButtonEventInfo != null)\r
+ {\r
+#if DEBUG\r
+ Console.WriteLine(" BTN: " + mouseButtonEventInfo.FrameOfApplication + "\t" + mouseButtonEventInfo.IsButtonDown);\r
+#endif\r
+\r
+ mGame.ApplyMouseButtonInput(gamerInfo, mouseButtonEventInfo.IsButtonDown);\r
+ continue;\r
+ }\r
\r
- mGame.Update(mTargetTimeSpan);\r
+ MouseMotionEventInfo mouseMotionEventInfo = eventInfo as MouseMotionEventInfo;\r
+ if (mouseMotionEventInfo != null)\r
+ {\r
+#if DEBUG\r
+ Console.WriteLine(" MMV: " + mouseMotionEventInfo.FrameOfApplication + "\t" + mouseMotionEventInfo.X + "," + mouseMotionEventInfo.Y);\r
+#endif\r
+\r
+ mGame.ApplyMouseLocationInput(gamerInfo, mouseMotionEventInfo.X, mouseMotionEventInfo.Y);\r
+ continue;\r
+ }\r
}\r
}\r
\r
- void DrawTestHarness(GameTime gameTime, SpriteBatch spriteBatch)\r
+\r
+ int CurrentAverageOneWayDelay\r
{\r
+ get\r
+ {\r
+ Debug.Assert(mNetworkSession != null);\r
\r
- // BEGIN: Test harness stuff.\r
- if (paused && gameTime.TotalRealTime.Milliseconds < 500)\r
- spriteBatch.DrawString(font, "-=> Paused <=-", new Vector2(10, 130), Color.White);\r
+ double numRemoteGamersTwice = 2 * mNetworkSession.RemoteGamers.Count;\r
+ double averageOwd = 0;\r
\r
- spriteBatch.DrawString(font, "Press [F1]...[F4] to simulate input for each player. Click X's to end game or terminate player.", new Vector2(10, 540), Color.White);\r
- spriteBatch.DrawString(font, "Press [ESC] to exit and [F12] to pause/unpause. Game auto-pauses every 1000 frames.", new Vector2(10, 570), Color.White);\r
- //END: Test harness stuff.\r
+ foreach (NetworkGamer gamer in mNetworkSession.RemoteGamers)\r
+ {\r
+ TimeSpan timeSpan = gamer.RoundtripTime;\r
+ averageOwd += timeSpan.TotalMilliseconds;\r
+ }\r
\r
+ return (int)((averageOwd / numRemoteGamersTwice) / 16.6666);\r
+ }\r
}\r
+\r
+ #endregion\r
}\r
}\r