From f58af70a5768c1d99ca535fb214565ba226f3f0f Mon Sep 17 00:00:00 2001 From: Charles Date: Sat, 10 Apr 2010 07:39:18 +0000 Subject: [PATCH] Implemented the base architecture we are bound to because of how the network code works. It's kind of like a walking skeleton... or a limping skeleton. git-svn-id: https://bd85.net/svn/cs3505_group@57 92bb83a3-7c8f-8a45-bc97-515c4e399668 --- CarFire/CarFire/CarFire.suo | Bin 12288 -> 23040 bytes CarFire/CarFire/CarFire/CarFire.csproj | 9 +- CarFire/CarFire/CarFire/ChatInfo.cs | 52 + CarFire/CarFire/CarFire/Game.cs | 87 ++ CarFire/CarFire/CarFire/IDeterministicGame.cs | 157 +++ CarFire/CarFire/CarFire/IScreenManager.cs | 19 + CarFire/CarFire/CarFire/NetworkManager.cs | 1065 +++++++++++++++++ CarFire/CarFire/CarFire/Program.cs | 2 +- CarFire/CarFire/CarFire/ScreenManager.cs | 35 + .../CarFire/CarFire/{Game1.cs => XnaGame.cs} | 40 +- 10 files changed, 1452 insertions(+), 14 deletions(-) create mode 100644 CarFire/CarFire/CarFire/ChatInfo.cs create mode 100644 CarFire/CarFire/CarFire/Game.cs create mode 100644 CarFire/CarFire/CarFire/IDeterministicGame.cs create mode 100644 CarFire/CarFire/CarFire/IScreenManager.cs create mode 100644 CarFire/CarFire/CarFire/NetworkManager.cs create mode 100644 CarFire/CarFire/CarFire/ScreenManager.cs rename CarFire/CarFire/CarFire/{Game1.cs => XnaGame.cs} (65%) diff --git a/CarFire/CarFire/CarFire.suo b/CarFire/CarFire/CarFire.suo index 8ebdb70438dcc4f4be891a99c91ecdf972b1070d..dd09ba19fe9e56eed8d46944c6f342d3069547ce 100644 GIT binary patch literal 23040 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 CDcL{( delta 858 zcmZ`$OGs2v7(VCDxpwb$oJJjOyx!Dd@X__rXl4>hqc)n3IWsCE1Yv<`HM1Kf2b8-I zf>y))&?<;PiUD-%S8k&qQw*v;_W}TnRXrcIN$mH?|+L7#|m5^dPQW?@?2nbF#z`DfX zju@6nF)GLNs^MaAAjT!TAg>-TxLDJSQ7)XuJ%DIMwB@vR+~Iw}x^Ffok3|T34nt<7l8Ac20M~YRg(r!Qfk1X=%UE~Rx#Iliv&o9mSHs94?{sOqz0PM;ERmGQcRb* zNkpU|))#?a0|7dZEMz;C!76iea7Cag+x|W1Hh*7M1*|j+OCt(sy-ui#Il&XX4~EtN zbSm$7u_%(RjGm;^FsG03`RNV#sYmFEfA>l@8ih01ogr`m?XQI&gLOFB jJZ$#fgQ4aQn3^;)qZ()}^}K|58+{L8Zz61NPjt~grZ4~J diff --git a/CarFire/CarFire/CarFire/CarFire.csproj b/CarFire/CarFire/CarFire/CarFire.csproj index 994f3b3..9d05f39 100644 --- a/CarFire/CarFire/CarFire/CarFire.csproj +++ b/CarFire/CarFire/CarFire/CarFire.csproj @@ -70,6 +70,7 @@ False + False @@ -83,9 +84,15 @@ + + + + + - + + diff --git a/CarFire/CarFire/CarFire/ChatInfo.cs b/CarFire/CarFire/CarFire/ChatInfo.cs new file mode 100644 index 0000000..fa6c0d1 --- /dev/null +++ b/CarFire/CarFire/CarFire/ChatInfo.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework.Net; + +namespace CarFire +{ + /// + /// Small container class for the information concerning a chat packet. + /// It is immutable. + /// + public class ChatInfo + { + // Private member variables + #region Instance Variables + + NetworkGamer mSender; + String mMessage; + + #endregion + + + /// + /// Get the sender who sent the chat packet. + /// + public NetworkGamer Sender + { + get { return mSender; } + } + + /// + /// Get the message that was sent by the sender. + /// + public String Message + { + get { return mMessage; } + } + + + /// + /// Construct a chat packet with contents. + /// + /// The chat sender. + /// The chat message. + public ChatInfo(NetworkGamer sender, String message) + { + mSender = sender; + mMessage = message; + } + } +} diff --git a/CarFire/CarFire/CarFire/Game.cs b/CarFire/CarFire/CarFire/Game.cs new file mode 100644 index 0000000..1650634 --- /dev/null +++ b/CarFire/CarFire/CarFire/Game.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +namespace CarFire +{ + class Game : IDeterministicGame + { + #region IDeterministicGame Members + + public void LoadContent(ContentManager contentManager) + { + } + + public void UnloadContent() + { + } + + public Vector2 PreferredScreenSize + { + get { return new Vector2(800, 600); } + } + + public int MinimumSupportedPlayers + { + get { return 1; } + } + + public int MaximumSupportedPlayers + { + get { return 4; } + } + + public void ResetGame(object[] playerIdentifiers, object thisPlayer) + { + } + + public long CurrentFrameNumber + { + get { return 0; } + } + + public long CurrentChecksum + { + get { return 0; } + } + + public void ApplyKeyInput(object playerIdentifier, Keys key, bool isKeyPressed) + { + } + + public void ApplyMouseLocationInput(object playerIdentifier, int x, int y) + { + } + + public void ApplyMouseButtonInput(object playerIdentifier, bool isButtonPressed) + { + } + + public bool IsGameOver(object playerIdentifier) + { + return true; + } + + public bool IsTerminated(object playerIdentifier) + { + return true; + } + + public long Update(TimeSpan timespan) + { + return CurrentFrameNumber; + } + + public long Draw(SpriteBatch spriteBatch) + { + return CurrentFrameNumber; + } + + #endregion + } +} diff --git a/CarFire/CarFire/CarFire/IDeterministicGame.cs b/CarFire/CarFire/CarFire/IDeterministicGame.cs new file mode 100644 index 0000000..857f57a --- /dev/null +++ b/CarFire/CarFire/CarFire/IDeterministicGame.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Content; + +namespace CarFire +{ + /// + /// A DeterministicGame object is a full XNA game, except that it does not + /// extend the Microsoft.Xna.Framework.Game class. It supports content loading + /// and unloading, as well as modified Update and Draw functionality. + /// + /// DeterministicGame objects are intented to be incorporated inside of an + /// existing game. By simply calling update and draw at the appropriate times, + /// and by supplying user inputs, the game will play just like any other game. + /// + /// It is intended that a DeterministicGame be a multiplayer game, and support for + /// this is listed in the interface below. Each player is identified by a unique object + /// reference (of the caller's choice, not a struct). The game supports the notion of a + /// current 'frame', or state. The enclosing code supplies the user inputs for the + /// next frame by calling methods. The enclosing code then should call the update + /// method to advance the game to the next frame. Finally, the enclosing code + /// calls the draw method to render the game state. Note that the game state can + /// be drawn multiple times without updating the game, thus allowing the game + /// to be paused or stalled. + /// + public interface IDeterministicGame + { + /// + /// Call this method to give the game a chance to load its content. + /// + /// A valid content manager pointing to the root of the content tree + void LoadContent (ContentManager contentManager); + + /// + /// Call this method to give the game a chance to unload its content. + /// + void UnloadContent(); + + /// + /// Returns the preferred screen size for this game. + /// + /// + Vector2 PreferredScreenSize { get; } + + /// + /// Returns the minimum number of players this game can support. + /// + /// the minimum player count + int MinimumSupportedPlayers { get; } + + /// + /// Returns the maximum number of players this game can support. + /// + /// the maximum player count + int MaximumSupportedPlayers { get; } + + /// + /// Call this method to reset the game state, to set the current frame at 0, and + /// to supply identifiers for each player in the game. Player identifiers should + /// be unique object references (not structs) that the caller will use later + /// to identify each player. (It is important that these not be 'boxed' object + /// references or the reference will not be preserved.) + /// + /// Since, in theory, there will be four copies of the game running, a second + /// parameter identifies the player that is running this copy of the game. + /// + /// An array of objects (references) that will identify each player + /// An object identifier for the player whose machine is displaying this game + void ResetGame(Object[] playerIdentifiers, Object thisPlayer); + + /// + /// Returns the current frame number. This corresponds to the current state + /// of the game world. + /// + /// the current frame number + long CurrentFrameNumber { get; } + + /// + /// Returns a checksum of all of the game world state. This checksum can be used + /// to ensure that multiple players at some frame all share the same state. It is + /// guaranteed that identical states will produce identical checksums. + /// + /// the current game state checksum + long CurrentChecksum { get; } + + /// + /// Call this method to report changes in keypresses to the game. You should call this method + /// to report any changes in keyboard state for a player. The keyboard state will be + /// applied to the next game state (not the current state). + /// + /// An object (reference) that was registered for a player in the game + /// A key identifier + /// The key state - true means pressed, false means released + void ApplyKeyInput (Object playerIdentifier, Keys key, bool isKeyPressed); + + /// + /// Call this method to report changes in mouse locations to the game. You should call this method + /// any time the mouse coordinates for a player changes. The mouse information will + /// be applied to the next game state (not the current state). + /// + /// an object (reference) that was registered for a player in the game + /// the mouse x location + /// the mouse y location + void ApplyMouseLocationInput (Object playerIdentifier, int x, int y); + + /// + /// Call this method to report changes in mouse button state to the game. Note that only one + /// mouse button is supported in game. You should call this method to report any + /// changes in mouse button state for a player. The mouse button state will be + /// applied to the next game state (not the current state). + /// + /// an object (reference) that was registered for a player in the game + /// the mouse button state + void ApplyMouseButtonInput (Object playerIdentifier, bool isButtonPressed); + + /// + /// Returns true if the specified player's game is over. They can be safely disconnected from the game + /// when this flag is true, their inputs do not affect game state. (You can continue to report inputs, + /// to allow the player to view a game over screen, but no game state action is taken.) + /// + /// an object (reference) that was registered for a player in the game + /// true if the game is over + bool IsGameOver(Object playerIdentifier); + + /// + /// Returns true if the specified player's game is over, and the player has clicked on something indicating + /// they wish to leave the game over screen. (This only becomes true if inputs are reported + /// even after the game is over.) + /// + /// an object (reference) that was registered for a player in the game + /// true if the player has terminated the game + bool IsTerminated(Object playerIdentifier); + + /// + /// Call this method to advance the game state. Previously sent inputs are applied + /// to the game state and the frame number is advanced and returned. Caution should be used when + /// supplying the seconds parameter - it can affect game state. All players in a game + /// should advance their game time by the same amount. + /// + /// The elapsed game time + /// the frame number of the new game state (now the current state) + long Update(TimeSpan timespan); + + /// + /// Draws the current game state. This does not affect the game state - it may be called + /// repeatedly to redraw the current game state if needed. + /// + /// a SpriteBatch object that has begun a batch + /// the current game state frame number + long Draw(SpriteBatch spriteBatch); + } +} diff --git a/CarFire/CarFire/CarFire/IScreenManager.cs b/CarFire/CarFire/CarFire/IScreenManager.cs new file mode 100644 index 0000000..1b15e20 --- /dev/null +++ b/CarFire/CarFire/CarFire/IScreenManager.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +namespace CarFire +{ + public interface IScreenManager + { + void LoadContent(ContentManager contentManager, GraphicsDeviceManager graphics); + void UnloadContent(); + long Update(GameTime gameTime, NetworkManager networkGame); + long Draw(SpriteBatch spriteBatch); + } +} diff --git a/CarFire/CarFire/CarFire/NetworkManager.cs b/CarFire/CarFire/CarFire/NetworkManager.cs new file mode 100644 index 0000000..3fd4ca4 --- /dev/null +++ b/CarFire/CarFire/CarFire/NetworkManager.cs @@ -0,0 +1,1065 @@ + +// 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 + } +} diff --git a/CarFire/CarFire/CarFire/Program.cs b/CarFire/CarFire/CarFire/Program.cs index 7dad1ae..8a00895 100644 --- a/CarFire/CarFire/CarFire/Program.cs +++ b/CarFire/CarFire/CarFire/Program.cs @@ -9,7 +9,7 @@ namespace CarFire /// static void Main(string[] args) { - using (Game1 game = new Game1()) + using (XnaGame game = new XnaGame()) { game.Run(); } diff --git a/CarFire/CarFire/CarFire/ScreenManager.cs b/CarFire/CarFire/CarFire/ScreenManager.cs new file mode 100644 index 0000000..5c6bf71 --- /dev/null +++ b/CarFire/CarFire/CarFire/ScreenManager.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace CarFire +{ + class ScreenManager : IScreenManager + { + #region ILobby Members + + public void LoadContent(ContentManager contentManager, GraphicsDeviceManager graphics) + { + } + + public void UnloadContent() + { + } + + public long Update(GameTime gameTime, NetworkManager networkGame) + { + return 0; + } + + public long Draw(SpriteBatch spriteBatch) + { + return 0; + } + + #endregion + } +} diff --git a/CarFire/CarFire/CarFire/Game1.cs b/CarFire/CarFire/CarFire/XnaGame.cs similarity index 65% rename from CarFire/CarFire/CarFire/Game1.cs rename to CarFire/CarFire/CarFire/XnaGame.cs index 0b9d9fe..0623ae9 100644 --- a/CarFire/CarFire/CarFire/Game1.cs +++ b/CarFire/CarFire/CarFire/XnaGame.cs @@ -16,15 +16,30 @@ namespace CarFire /// /// This is the main type for your game /// - public class Game1 : Microsoft.Xna.Framework.Game + public class XnaGame : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; - public Game1() + NetworkManager networkGame; + IScreenManager screenManager; + IDeterministicGame deterministicGame; + + public XnaGame() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; + + Components.Add(new GamerServicesComponent(this)); + + screenManager = new ScreenManager(); + deterministicGame = new Game(); + networkGame = new NetworkManager(screenManager, deterministicGame); + + Vector2 size = deterministicGame.PreferredScreenSize; + graphics.PreferredBackBufferWidth = (int)size.X; + graphics.PreferredBackBufferHeight = (int)size.Y; + graphics.ApplyChanges(); } /// @@ -35,7 +50,8 @@ namespace CarFire /// protected override void Initialize() { - // TODO: Add your initialization logic here + IsFixedTimeStep = true; + TargetElapsedTime = networkGame.TargetTimeSpan; base.Initialize(); } @@ -49,7 +65,8 @@ namespace CarFire // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); - // TODO: use this.Content to load your game content here + screenManager.LoadContent(Content, graphics); + deterministicGame.LoadContent(Content); } /// @@ -58,7 +75,8 @@ namespace CarFire /// protected override void UnloadContent() { - // TODO: Unload any non ContentManager content here + screenManager.UnloadContent(); + deterministicGame.UnloadContent(); } /// @@ -68,11 +86,7 @@ namespace CarFire /// Provides a snapshot of timing values. protected override void Update(GameTime gameTime) { - // Allows the game to exit - if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) - this.Exit(); - - // TODO: Add your update logic here + networkGame.Update(gameTime); base.Update(gameTime); } @@ -83,9 +97,11 @@ namespace CarFire /// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime) { - GraphicsDevice.Clear(Color.CornflowerBlue); + GraphicsDevice.Clear(Color.Red); - // TODO: Add your drawing code here + spriteBatch.Begin(); + networkGame.Draw(gameTime, spriteBatch); + spriteBatch.End(); base.Draw(gameTime); } -- 2.45.2