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; namespace CS_3505_Project_06 { /// /// A manager class to handle network interactions between peers and /// lobby/game switching. /// public class NetworkGame { /// /// 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); /// /// 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; } /// /// 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); } private void CreateSessionEnd(IAsyncResult result) { Debug.Assert(mNetworkSession == null); mNetworkSession = NetworkSession.EndCreate(result); mNetworkSession.AllowHostMigration = true; mNetworkSession.AllowJoinInProgress = false; mJoinedSessionDelegate(mNetworkSession, this); } /// /// 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); } private void FindSessionsEnd(IAsyncResult result) { AvailableNetworkSessionCollection sessions = NetworkSession.EndFind(result); mFoundSessionsDelegate(sessions, this); } /// /// 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); } private void JoinSessionEnd(IAsyncResult result) { Debug.Assert(mNetworkSession == null); mNetworkSession = NetworkSession.EndJoin(result); 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.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(); ReadPackets(); if (mNetworkSession.SessionState == NetworkSessionState.Lobby) { if (mNetworkSession.IsHost && mNetworkSession.AllGamers.Count >= mGame.MinimumSupportedPlayers && mNetworkSession.IsEveryoneReady) { mNetworkSession.StartGame(); mNetworkSession.ResetReady(); } else { mLobby.Update(gameTime, this); } } else if (mNetworkSession.SessionState == NetworkSessionState.Playing) { if (HaveNeededEvents) { if (IsLatencyAdjustmentFrame) AdjustLatency(); mStallCount = 0; SendLocalEvents(); ApplyEvents(); mGame.Update(mTargetTimeSpan); } else // Stall! { } } } } /// /// 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) { mLobby.Draw(spriteBatch); } } } /// /// Get the chat messages that have been receive 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) { WriteChat(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) { WriteChat(message); LocalGamer.SendData(mPacketWriter, SendDataOptions.ReliableInOrder, recipient); } // 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 mLastPressedKeys = new List(); bool mLastButtonPressed; int mLatency; long mNextLatencyAdjustmentFrame; int mStallCount; int mAverageOwd; TimeSpan mTargetTimeSpan = new TimeSpan(166666); public TimeSpan TargetTimeSpan { get { return mTargetTimeSpan; } } #endregion // Private implementation methods of the network protocol #region Private Implementation Methods enum PacketType { Chat = 1, Event = 2, Stall = 3 } enum EventType { KeyDown = 1, KeyUp = 2, MouseDown = 3, MouseUp = 4, MouseMove = 5 } /// /// Reinitialize the private variables in preparation for new game to start. /// void Reset() { mLatency = 1; mNextLatencyAdjustmentFrame = 1; mStallCount = 0; mAverageOwd = AverageOneWayDelay; // TODO: The game object needs to be reset, too. //mGame.ResetGame(playerIdentifiers, playerIdentifiers[0]); } /// /// 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. void ReadPackets() { foreach (LocalNetworkGamer gamer in mNetworkSession.LocalGamers) { while (gamer.IsDataAvailable) { NetworkGamer sender; gamer.ReceiveData(mPacketReader, out sender); PacketType packetId = (PacketType)mPacketReader.ReadByte(); switch (packetId) { case PacketType.Chat: short messageLength = mPacketReader.ReadInt16(); char[] message = mPacketReader.ReadChars(messageLength); ChatPacket chatPacket = new ChatPacket(sender, new String(message)); mChatPackets.Add(chatPacket); break; case PacketType.Event: short stallCount = mPacketReader.ReadInt16(); short averageOwd = mPacketReader.ReadInt16(); int frameNumber = mPacketReader.ReadInt32(); byte numEvents = mPacketReader.ReadByte(); for (byte i = 0; i < numEvents; ++i) { ReadEvent(mPacketReader, sender); } break; case PacketType.Stall: byte numStalledPeers = mPacketReader.ReadByte(); byte[] stalledPeers = mPacketReader.ReadBytes(numStalledPeers); break; } } } } void ReadEvent(PacketReader packetReader, NetworkGamer sender) { EventType eventId = (EventType)packetReader.ReadByte(); long applicationFrame = packetReader.ReadInt32(); switch (eventId) { case EventType.KeyDown: int keyCode1 = packetReader.ReadInt32(); break; case EventType.KeyUp: int keyCode2 = packetReader.ReadInt32(); break; case EventType.MouseDown: byte buttonId1 = packetReader.ReadByte(); break; case EventType.MouseUp: byte buttonId2 = packetReader.ReadByte(); break; case EventType.MouseMove: short x = packetReader.ReadInt16(); short y = packetReader.ReadInt16(); break; } } void WriteChat(String message) { mPacketWriter.Write((byte)PacketType.Chat); mPacketWriter.Write((short)message.Length); mPacketWriter.Write(message.ToCharArray()); } bool IsLatencyAdjustmentFrame { get { // TODO return false; } } void AdjustLatency() { // TODO } void SendLocalEvents() { // TODO: Not finished. KeyboardState keyState = Keyboard.GetState(); MouseState mouseState = Mouse.GetState(); // Make a list of the keys pressed or released this frame. 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; mLastPressedKeys = new List(pressedKeysArray); bool buttonPressed = mouseState.LeftButton == ButtonState.Pressed; } bool HaveNeededEvents { get { // TODO return true; } } void ApplyEvents() { // TODO } int AverageOneWayDelay { get { // TODO return 12; } } #endregion } }