X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fcarfire;a=blobdiff_plain;f=Project06%2FCS%203505%20Project%2006%2FCS%203505%20Project%2006%2FNetworkGame.cs;h=45c5d0a498fdd5a1ce9de1445b85cdbd309a6187;hp=52486c8b5f06b75f066710d1635755b66ad7f450;hb=b98320a31a73dc18d6ef83cb440a5304930af161;hpb=4ac7a5127df67229deb64907208ff78dc3869aac diff --git a/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs b/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs index 52486c8..45c5d0a 100644 --- a/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs +++ b/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs @@ -1,4 +1,7 @@ -using System; + +#undef DEBUG + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,67 +11,78 @@ using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; +using System.Collections; namespace CS_3505_Project_06 { + /// + /// A manager class to handle network interactions between peers and + /// lobby/game switching. + /// public class NetworkGame { - NetworkSession mNetworkSession; - - ILobby mLobby; - IDeterministicGame mGame; - - TimeSpan mTargetTimeSpan = new TimeSpan(166666); - public TimeSpan TargetTimeSpan - { - get - { - return mTargetTimeSpan; - } - } - - List lastPressedKeys; - bool lastButtonPressed; - - Object[] playerIdentifiers = { "One", "Two", "Three", "Four" }; // Any objects will do, strings are easy to debug. - - // For debugging - - Object activePlayer; - bool paused; - long lastAutoPause; - - public SpriteFont font; - - + // 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, NetworkGame 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, NetworkGame 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, NetworkGame 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 NetworkGame(ILobby lobby, IDeterministicGame game) { Debug.Assert(lobby != null && game != null); mLobby = lobby; mGame = game; - - // Begin: Test harness stuff - lastPressedKeys = new List(); - activePlayer = playerIdentifiers[0]; - paused = false; - - // Reset the game - indicate that player #1 (player 0) owns this instance of the game. - - mGame.ResetGame(playerIdentifiers, playerIdentifiers[0]); } + /// + /// 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]; } } - // I added this as I needed a way to display all gamers not just the first gamer - // -Brady + /// + /// Get all the gamers associated with the active network session. + /// public GamerCollection NetworkGamers { get @@ -78,44 +92,130 @@ namespace CS_3505_Project_06 } - public NetworkSession CreateSession() + /// + /// 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) { - return CreateSession(mGame.MaximumSupportedPlayers); + CreateSession(mGame.MaximumSupportedPlayers, callback); } - public NetworkSession CreateSession(int maxGamers) + /// + /// 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); + Debug.Assert(mNetworkSession == null); - mNetworkSession = NetworkSession.Create(NetworkSessionType.SystemLink, 1, maxGamers); - mNetworkSession.AllowHostMigration = true; - mNetworkSession.AllowJoinInProgress = false; + mJoinedSessionDelegate = callback; + NetworkSession.BeginCreate(NetworkSessionType.SystemLink, 1, maxGamers, CreateSessionEnd, null); + } + void CreateSessionEnd(IAsyncResult result) + { + Debug.Assert(mNetworkSession == null); - return mNetworkSession; + 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(); } - // added so I can test if sessionExists and thus be able to call things on NetworkGame safely - // -Brady - public bool sessionExists() + /// + /// 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 { - return mNetworkSession != null; + get + { + return mNetworkSession != null; + } } - public AvailableNetworkSessionCollection FindSessions() + + /// + /// 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) { - return NetworkSession.Find(NetworkSessionType.SystemLink, 1, new NetworkSessionProperties()); + 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; } - public NetworkSession JoinSession(AvailableNetworkSession availableSession) + /// + /// 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); - mNetworkSession = NetworkSession.Join(availableSession); + mJoinedSessionDelegate = callback; + NetworkSession.BeginJoin(availableSession, JoinSessionEnd, null); + } + void JoinSessionEnd(IAsyncResult result) + { + Debug.Assert(mNetworkSession == null); - return mNetworkSession; + 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); @@ -125,6 +225,9 @@ namespace CS_3505_Project_06 } + /// + /// Set up the network session to simulate 200ms latency and 10% packet loss. + /// public void SimulateBadNetwork() { Debug.Assert(mNetworkSession != null); @@ -134,6 +237,34 @@ namespace CS_3505_Project_06 } + /// + /// 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) @@ -143,132 +274,806 @@ namespace CS_3505_Project_06 else { mNetworkSession.Update(); + HandleIncomingPackets(); if (mNetworkSession.SessionState == NetworkSessionState.Lobby) { - if (mNetworkSession.IsHost && - mNetworkSession.AllGamers.Count >= mGame.MinimumSupportedPlayers && - mNetworkSession.IsEveryoneReady) + mLobby.Update(gameTime, this); + } + else if (mNetworkSession.SessionState == NetworkSessionState.Playing) + { + if (mGame.IsGameOver(LocalGamerInfo) || mGame.IsTerminated(LocalGamerInfo)) { - mNetworkSession.StartGame(); - mNetworkSession.ResetReady(); + // TODO: Should support moving back to the session lobby. + LeaveSession(); + return; } - else + + if (HaveNeededEvents) { - mLobby.Update(gameTime, this); + 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! + { + if (mStallCount == 0) + { +#if DEBUG + Console.WriteLine("STAL: ===="); +#endif + } + else if (mStallCount % 60 == 0) + { + // DEBUG + //Console.WriteLine("Stalled for " + mStallCount + " frames."); + } + + mStallCount++; + + // Send a reliable event packet to each stalled gamer. + if (mStallCount == 1) + { + foreach (GamerInfo gamerInfo in GamerArray) + { + if (gamerInfo.HighestFrameNumber < mGame.CurrentFrameNumber) + { + SendLocalEvents(gamerInfo.Gamer); + } + } + } + + /*if (mStallCount > StallTimeout) + { + DropLostGamers(); + mStallCount = 0; + } + else if (mStallCount == 1) + { + SendLocalEvents + } + else if (mStallCount % 60 == 0) + { + } TODO */ + } + } + } + } + + /// + /// 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) { - // TODO: in-game update stuff - UpdateTestHarness(gameTime); + 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; + + ILobby 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, + Stall = 3 + } + + 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); + if (sender == null || sender.IsDisposed) continue; + GamerInfo senderInfo = mGamers[sender.Id]; + + 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: + + 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; + + case PacketType.Stall: + + byte numStalledPeers = mPacketReader.ReadByte(); + byte[] stalledPeers = mPacketReader.ReadBytes(numStalledPeers); + + // TODO + break; + + default: + + Console.WriteLine("Received unknown packet type: " + (int)packetId); + break; + } + } + } - mGame.Update(mTargetTimeSpan); + + 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; } } } - public void Draw(GameTime gameTime, SpriteBatch spriteBatch) + + bool IsLatencyAdjustmentFrame { - mLobby.Draw(spriteBatch); - DrawTestHarness(gameTime, spriteBatch); + 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 - void UpdateTestHarness(GameTime gameTime) + 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() { - // Get user's input state. + List events = new List(); - KeyboardState keyState = Keyboard.GetState(); - MouseState mouseState = Mouse.GetState(); + long frameOfApplication = mGame.CurrentFrameNumber + mLatency; + if (frameOfApplication <= mHighestFrameNumber) return events; + else mHighestFrameNumber = frameOfApplication; - // Make a list of the keys pressed or released this frame. + // 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 (!lastPressedKeys.Contains(k)) - pressedKeys.Add(k); - else - lastPressedKeys.Remove(k); - - releasedKeys = lastPressedKeys; - lastPressedKeys = new List(pressedKeysArray); + { + if (!mLastPressedKeys.Contains(k)) pressedKeys.Add(k); + else mLastPressedKeys.Remove(k); + } - // Get mouse button state. + releasedKeys = mLastPressedKeys; - bool buttonPressed = mouseState.LeftButton == ButtonState.Pressed; + 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)); + } - /***** Begining of game logic. *****/ +#if DEBUG + if (pressedKeys.Contains(Keys.Escape)) mDontSendEvents = true; + if (releasedKeys.Contains(Keys.Escape)) mDontSendEvents = false; +#endif - // Debug - allow user on this machine to direct input to any player's state in the game. + // 2. Find the mouse differences. - if (pressedKeys.Contains(Keys.F1)) activePlayer = playerIdentifiers[0]; - if (pressedKeys.Contains(Keys.F2)) activePlayer = playerIdentifiers[1]; - if (pressedKeys.Contains(Keys.F3)) activePlayer = playerIdentifiers[2]; - if (pressedKeys.Contains(Keys.F4)) activePlayer = playerIdentifiers[3]; + MouseState mouseState = Mouse.GetState(); - // Debug - allow user on this machine to pause/resume game state advances. + bool leftButtonPressed = mouseState.LeftButton == ButtonState.Pressed; + if (leftButtonPressed != mLastLeftButtonPressed) + { + events.Add(new MouseButtonEventInfo(LocalGamer, frameOfApplication, MouseButton.Left, leftButtonPressed)); + } - if (pressedKeys.Contains(Keys.F12) || - pressedKeys.Contains(Keys.P) && (keyState.IsKeyDown(Keys.LeftControl) || keyState.IsKeyDown(Keys.RightControl))) + bool rightButtonPressed = mouseState.RightButton == ButtonState.Pressed; + if (rightButtonPressed != mLastRightButtonPressed) { - paused = !paused; - return; // Don't update on pause start or stop + events.Add(new MouseButtonEventInfo(LocalGamer, frameOfApplication, MouseButton.Right, rightButtonPressed)); } - // Debug - automatically pause every 1000 frames. + bool middleButtonPressed = mouseState.MiddleButton == ButtonState.Pressed; + if (middleButtonPressed != mLastMiddleButtonPressed) + { + events.Add(new MouseButtonEventInfo(LocalGamer, frameOfApplication, MouseButton.Middle, middleButtonPressed)); + } - if (mGame.CurrentFrameNumber % 1000 == 0 && mGame.CurrentFrameNumber != lastAutoPause) + int mousePositionX = mouseState.X; + int mousePositionY = mouseState.Y; + if (mousePositionX != mLastMousePositionX || mousePositionY != mLastMousePositionY) { - paused = true; - lastAutoPause = mGame.CurrentFrameNumber; + 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); + } + } - //if (pressedKeys.Contains(Keys.Escape)) - // this.Exit(); + void SendLocalEvents(NetworkGamer recipient) + { +#if DEBUG + if (mDontSendEvents) return; +#endif - // Game update + List events = new List(mLocalEvents); + events.AddRange(mLastLocalEvents); - // Direct inputs to the game engine - only report changes. + 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); + } + } - foreach (Keys k in pressedKeys) - mGame.ApplyKeyInput(activePlayer, k, true); - foreach (Keys k in releasedKeys) - mGame.ApplyKeyInput(activePlayer, k, false); + bool HaveNeededEvents + { + get + { + long currentFrame = mGame.CurrentFrameNumber; - mGame.ApplyMouseLocationInput(activePlayer, mouseState.X, mouseState.Y); + foreach (GamerInfo gamerInfo in mGamers.Values) + { + if (gamerInfo.HighestFrameNumber < currentFrame) return false; + } - if (lastButtonPressed != buttonPressed) - mGame.ApplyMouseButtonInput(activePlayer, buttonPressed); + return true; + } + } - lastButtonPressed = buttonPressed; + void ApplyEvents() + { + int index = CurrentEventArrayIndex; - if (!paused) + foreach (GamerInfo gamerInfo in GamerArray) { - // Advance the game engine. + if (gamerInfo.Events[index] == null) continue; - mGame.Update(mTargetTimeSpan); + foreach (EventInfo eventInfo in gamerInfo.Events[index]) + { + 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; + } + } + + gamerInfo.Events[index] = null; } } - void DrawTestHarness(GameTime gameTime, SpriteBatch spriteBatch) + + int CurrentAverageOneWayDelay { + get + { + Debug.Assert(mNetworkSession != null); - // BEGIN: Test harness stuff. - if (paused && gameTime.TotalRealTime.Milliseconds < 500) - spriteBatch.DrawString(font, "-=> Paused <=-", new Vector2(10, 130), Color.White); + double numRemoteGamersTwice = 2 * mNetworkSession.RemoteGamers.Count; + double averageOwd = 0; - 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); - spriteBatch.DrawString(font, "Press [ESC] to exit and [F12] to pause/unpause. Game auto-pauses every 1000 frames.", new Vector2(10, 570), Color.White); - //END: Test harness stuff. + foreach (NetworkGamer gamer in mNetworkSession.RemoteGamers) + { + TimeSpan timeSpan = gamer.RoundtripTime; + averageOwd += timeSpan.TotalMilliseconds; + } + return (int)((averageOwd / numRemoteGamersTwice) / 16.6666); + } } + + #endregion } }