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