--- /dev/null
+using System;\r
+using System.Collections.Generic;\r
+using System.Linq;\r
+using System.Text;\r
+using Microsoft.Xna.Framework;\r
+using Microsoft.Xna.Framework.Graphics;\r
+using Microsoft.Xna.Framework.Input;\r
+using Microsoft.Xna.Framework.Content;\r
+\r
+namespace CarFire\r
+{\r
+ /// <summary>\r
+ /// A DeterministicGame object is a full XNA game, except that it does not\r
+ /// extend the Microsoft.Xna.Framework.Game class. It supports content loading\r
+ /// and unloading, as well as modified Update and Draw functionality.\r
+ /// \r
+ /// DeterministicGame objects are intented to be incorporated inside of an \r
+ /// existing game. By simply calling update and draw at the appropriate times,\r
+ /// and by supplying user inputs, the game will play just like any other game.\r
+ /// \r
+ /// It is intended that a DeterministicGame be a multiplayer game, and support for\r
+ /// this is listed in the interface below. Each player is identified by a unique object\r
+ /// reference (of the caller's choice, not a struct). The game supports the notion of a\r
+ /// current 'frame', or state. The enclosing code supplies the user inputs for the\r
+ /// next frame by calling methods. The enclosing code then should call the update\r
+ /// method to advance the game to the next frame. Finally, the enclosing code\r
+ /// calls the draw method to render the game state. Note that the game state can\r
+ /// be drawn multiple times without updating the game, thus allowing the game\r
+ /// to be paused or stalled.\r
+ /// </summary>\r
+ public interface IDeterministicGame\r
+ {\r
+ /// <summary>\r
+ /// Call this method to give the game a chance to load its content.\r
+ /// </summary>\r
+ /// <param name="contentManager">A valid content manager pointing to the root of the content tree</param>\r
+ void LoadContent (ContentManager contentManager);\r
+\r
+ /// <summary>\r
+ /// Call this method to give the game a chance to unload its content.\r
+ /// </summary>\r
+ void UnloadContent();\r
+\r
+ /// <summary>\r
+ /// Returns the preferred screen size for this game.\r
+ /// </summary>\r
+ /// <returns></returns>\r
+ Vector2 PreferredScreenSize { get; }\r
+\r
+ /// <summary>\r
+ /// Returns the minimum number of players this game can support.\r
+ /// </summary>\r
+ /// <returns>the minimum player count</returns>\r
+ int MinimumSupportedPlayers { get; }\r
+\r
+ /// <summary>\r
+ /// Returns the maximum number of players this game can support.\r
+ /// </summary>\r
+ /// <returns>the maximum player count</returns>\r
+ int MaximumSupportedPlayers { get; }\r
+\r
+ /// <summary>\r
+ /// Call this method to reset the game state, to set the current frame at 0, and\r
+ /// to supply identifiers for each player in the game. Player identifiers should\r
+ /// be unique object references (not structs) that the caller will use later\r
+ /// to identify each player. (It is important that these not be 'boxed' object\r
+ /// references or the reference will not be preserved.)\r
+ /// \r
+ /// Since, in theory, there will be four copies of the game running, a second\r
+ /// parameter identifies the player that is running this copy of the game.\r
+ /// </summary>\r
+ /// <param name="playerIdentifiers">An array of objects (references) that will identify each player</param>\r
+ /// <param name="playerIdentifiers">An object identifier for the player whose machine is displaying this game</param>\r
+ void ResetGame(Object[] playerIdentifiers, Object thisPlayer);\r
+\r
+ /// <summary>\r
+ /// Returns the current frame number. This corresponds to the current state\r
+ /// of the game world.\r
+ /// </summary>\r
+ /// <returns>the current frame number</returns>\r
+ long CurrentFrameNumber { get; }\r
+\r
+ /// <summary>\r
+ /// Returns a checksum of all of the game world state. This checksum can be used\r
+ /// to ensure that multiple players at some frame all share the same state. It is\r
+ /// guaranteed that identical states will produce identical checksums.\r
+ /// </summary>\r
+ /// <returns>the current game state checksum</returns>\r
+ long CurrentChecksum { get; }\r
+\r
+ /// <summary>\r
+ /// Call this method to report changes in keypresses to the game. You should call this method\r
+ /// to report any changes in keyboard state for a player. The keyboard state will be\r
+ /// applied to the next game state (not the current state).\r
+ /// </summary>\r
+ /// <param name="playerIdentifier">An object (reference) that was registered for a player in the game</param>\r
+ /// <param name="key">A key identifier</param>\r
+ /// <param name="isKeyPressed">The key state - true means pressed, false means released</param>\r
+ void ApplyKeyInput (Object playerIdentifier, Keys key, bool isKeyPressed);\r
+\r
+ /// <summary>\r
+ /// Call this method to report changes in mouse locations to the game. You should call this method\r
+ /// any time the mouse coordinates for a player changes. The mouse information will\r
+ /// be applied to the next game state (not the current state).\r
+ /// </summary>\r
+ /// <param name="playerIdentifier">an object (reference) that was registered for a player in the game</param>\r
+ /// <param name="x">the mouse x location</param>\r
+ /// <param name="y">the mouse y location</param>\r
+ void ApplyMouseLocationInput (Object playerIdentifier, int x, int y);\r
+\r
+ /// <summary>\r
+ /// Call this method to report changes in mouse button state to the game. Note that only one\r
+ /// mouse button is supported in game. You should call this method to report any\r
+ /// changes in mouse button state for a player. The mouse button state will be\r
+ /// applied to the next game state (not the current state).\r
+ /// </summary>\r
+ /// <param name="playerIdentifier">an object (reference) that was registered for a player in the game</param>\r
+ /// <param name="isButtonPressed">the mouse button state</param>\r
+ void ApplyMouseButtonInput (Object playerIdentifier, bool isButtonPressed);\r
+\r
+ /// <summary>\r
+ /// Returns true if the specified player's game is over. They can be safely disconnected from the game\r
+ /// when this flag is true, their inputs do not affect game state. (You can continue to report inputs,\r
+ /// to allow the player to view a game over screen, but no game state action is taken.)\r
+ /// </summary>\r
+ /// <param name="playerIdentifier">an object (reference) that was registered for a player in the game</param>\r
+ /// <returns>true if the game is over</returns>\r
+ bool IsGameOver(Object playerIdentifier);\r
+\r
+ /// <summary>\r
+ /// Returns true if the specified player's game is over, and the player has clicked on something indicating\r
+ /// they wish to leave the game over screen. (This only becomes true if inputs are reported\r
+ /// even after the game is over.)\r
+ /// </summary>\r
+ /// <param name="playerIdentifier">an object (reference) that was registered for a player in the game</param>\r
+ /// <returns>true if the player has terminated the game</returns>\r
+ bool IsTerminated(Object playerIdentifier);\r
+\r
+ /// <summary>\r
+ /// Call this method to advance the game state. Previously sent inputs are applied\r
+ /// to the game state and the frame number is advanced and returned. Caution should be used when\r
+ /// supplying the seconds parameter - it can affect game state. All players in a game\r
+ /// should advance their game time by the same amount.\r
+ /// </summary>\r
+ /// <param name="timespan">The elapsed game time</param>\r
+ /// <returns>the frame number of the new game state (now the current state)</returns>\r
+ long Update(TimeSpan timespan);\r
+\r
+ /// <summary>\r
+ /// Draws the current game state. This does not affect the game state - it may be called\r
+ /// repeatedly to redraw the current game state if needed.\r
+ /// </summary>\r
+ /// <param name="spriteBatch">a SpriteBatch object that has begun a batch</param>\r
+ /// <returns>the current game state frame number</returns>\r
+ long Draw(SpriteBatch spriteBatch);\r
+ }\r
+}\r
--- /dev/null
+\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.Net;\r
+using System.Diagnostics;\r
+using Microsoft.Xna.Framework.GamerServices;\r
+using Microsoft.Xna.Framework.Graphics;\r
+using Microsoft.Xna.Framework;\r
+using Microsoft.Xna.Framework.Input;\r
+using System.Collections;\r
+\r
+namespace CarFire\r
+{\r
+ /// <summary>\r
+ /// A manager class to handle network interactions between peers and\r
+ /// lobby/game switching.\r
+ /// </summary>\r
+ public class NetworkManager\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, NetworkManager 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, NetworkManager 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, NetworkManager 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 NetworkManager(IScreenManager lobby, IDeterministicGame game)\r
+ {\r
+ Debug.Assert(lobby != null && game != null);\r
+\r
+ mLobby = lobby;\r
+ mGame = game;\r
+ }\r
+\r
+\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
+ /// <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
+ CreateSession(mGame.MaximumSupportedPlayers, callback);\r
+ }\r
+\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
+\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
+ 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
+ /// <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
+ 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
+ /// <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
+ mJoinedSessionDelegate = callback;\r
+ NetworkSession.BeginJoin(availableSession, JoinSessionEnd, null);\r
+ }\r
+ void JoinSessionEnd(IAsyncResult result)\r
+ {\r
+ Debug.Assert(mNetworkSession == null);\r
+\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
+ mNetworkSession.Dispose();\r
+ 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
+ mNetworkSession.SimulatedLatency = new TimeSpan(0, 0, 0, 0, 200);\r
+ mNetworkSession.SimulatedPacketLoss = 0.1f;\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
+ {\r
+ mLobby.Update(gameTime, this);\r
+ }\r
+ else\r
+ {\r
+ mNetworkSession.Update();\r
+ HandleIncomingPackets();\r
+\r
+ if (mNetworkSession.SessionState == NetworkSessionState.Lobby)\r
+ {\r
+ mLobby.Update(gameTime, this);\r
+ }\r
+ else if (mNetworkSession.SessionState == NetworkSessionState.Playing)\r
+ {\r
+ if (mGame.IsTerminated(LocalGamerInfo))\r
+ {\r
+ LeaveSession();\r
+ return;\r
+ }\r
+ else if (mGame.IsGameOver(LocalGamerInfo))\r
+ {\r
+ ApplyEvents(LocalGamerInfo, GetEventsFromInput());\r
+ mGame.Update(mTargetTimeSpan);\r
+ return;\r
+ }\r
+\r
+ if (HaveNeededEvents)\r
+ {\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
+ 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
+ IScreenManager 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
+ 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
+\r
+ int CurrentEventArrayIndex\r
+ {\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
+ 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
+ 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
+ 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
+\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
+ {\r
+ if (!mLastPressedKeys.Contains(k)) pressedKeys.Add(k);\r
+ else mLastPressedKeys.Remove(k);\r
+ }\r
+\r
+ releasedKeys = mLastPressedKeys;\r
+\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
+#if DEBUG\r
+ if (pressedKeys.Contains(Keys.Escape)) mDontSendEvents = true;\r
+ if (releasedKeys.Contains(Keys.Escape)) mDontSendEvents = false;\r
+#endif\r
+\r
+ // 2. Find the mouse differences.\r
+\r
+ MouseState mouseState = Mouse.GetState();\r
+\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
+ bool middleButtonPressed = mouseState.MiddleButton == ButtonState.Pressed;\r
+ if (middleButtonPressed != mLastMiddleButtonPressed)\r
+ {\r
+ events.Add(new MouseButtonEventInfo(LocalGamer, frameOfApplication, MouseButton.Middle, middleButtonPressed));\r
+ }\r
+\r
+ int mousePositionX = mouseState.X;\r
+ int mousePositionY = mouseState.Y;\r
+ if (mousePositionX != mLastMousePositionX || mousePositionY != mLastMousePositionY)\r
+ {\r
+ events.Add(new MouseMotionEventInfo(LocalGamer, frameOfApplication, mousePositionX, mousePositionY));\r
+ }\r
+\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
+ void SendLocalEvents(List<NetworkGamer> recipicents)\r
+ {\r
+ foreach (NetworkGamer gamer in recipicents)\r
+ {\r
+ SendLocalEvents(gamer);\r
+ }\r
+ }\r
+\r
+ void SendLocalEvents(NetworkGamer recipient)\r
+ {\r
+#if DEBUG\r
+ if (mDontSendEvents) return;\r
+#endif\r
+\r
+ List<EventInfo> events = new List<EventInfo>(mLocalEvents);\r
+ events.AddRange(mLastLocalEvents);\r
+\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
+\r
+ bool HaveNeededEvents\r
+ {\r
+ get\r
+ {\r
+ long currentFrame = mGame.CurrentFrameNumber;\r
+\r
+ foreach (GamerInfo gamerInfo in mGamers.Values)\r
+ {\r
+ if (mGame.IsGameOver(gamerInfo)) continue;\r
+ if (gamerInfo.HighestFrameNumber < currentFrame) return false;\r
+ }\r
+\r
+ return true;\r
+ }\r
+ }\r
+\r
+ void ApplyEvents()\r
+ {\r
+ int index = CurrentEventArrayIndex;\r
+\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
+ void ApplyEvents(GamerInfo gamerInfo, List<EventInfo> events)\r
+ {\r
+ foreach (EventInfo eventInfo in events)\r
+ {\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
+ 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
+\r
+ int CurrentAverageOneWayDelay\r
+ {\r
+ get\r
+ {\r
+ Debug.Assert(mNetworkSession != null);\r
+\r
+ double numRemoteGamersTwice = 2 * mNetworkSession.RemoteGamers.Count;\r
+ double averageOwd = 0;\r
+\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