diff --git a/Subsurface/Source/Characters/Animation/Ragdoll.cs b/Subsurface/Source/Characters/Animation/Ragdoll.cs index 2976cdfe4..e7a1da14d 100644 --- a/Subsurface/Source/Characters/Animation/Ragdoll.cs +++ b/Subsurface/Source/Characters/Animation/Ragdoll.cs @@ -702,9 +702,9 @@ namespace Barotrauma } if (isFrozen) { - for (int i=0;i < Limbs.Length;i++) + foreach (Limb l in Limbs) { - Limbs[i].body.PhysEnabled = true; + l.body.PhysEnabled = true; } isFrozen = false; } diff --git a/Subsurface/Source/Characters/Character.cs b/Subsurface/Source/Characters/Character.cs index 202a899d0..a4fd23fcb 100644 --- a/Subsurface/Source/Characters/Character.cs +++ b/Subsurface/Source/Characters/Character.cs @@ -27,6 +27,7 @@ namespace Barotrauma get { return netStateID; } } + byte dequeuedInput = 0; byte prevDequeuedInput = 0; List memInput = new List(); List memMouseX = new List(); List memMouseY = new List(); @@ -74,7 +75,7 @@ namespace Barotrauma private CharacterInventory inventory; - public float LastNetworkUpdate; + private UInt32 LastNetworkUpdateID = 0; //public int LargeUpdateTimer; @@ -638,11 +639,54 @@ namespace Barotrauma public bool IsKeyHit(InputType inputType) { + if (GameMain.Server != null && Character.Controlled != null) + { + switch (inputType) + { + case InputType.Left: + return ((dequeuedInput & 1) > 0) && !((prevDequeuedInput & 1) > 0); + //break; + case InputType.Right: + return ((dequeuedInput & 2) > 0) && !((prevDequeuedInput & 2) > 0); + //break; + case InputType.Up: + return ((dequeuedInput & 4) > 0) && !((prevDequeuedInput & 4) > 0); + //break; + case InputType.Down: + return ((dequeuedInput & 8) > 0) && !((prevDequeuedInput & 8) > 0); + //break; + default: + return false; + //break; + } + } + return keys[(int)inputType].Hit; } public bool IsKeyDown(InputType inputType) { + if (GameMain.Server!=null && Character.Controlled!=this) + { + bool retVal = false; + switch (inputType) + { + case InputType.Left: + retVal = (dequeuedInput & 1) > 0; + break; + case InputType.Right: + retVal = (dequeuedInput & 2) > 0; + break; + case InputType.Up: + retVal = (dequeuedInput & 4) > 0; + break; + case InputType.Down: + retVal = (dequeuedInput & 8) > 0; + break; + } + return retVal; + } + return keys[(int)inputType].Held; } @@ -1146,7 +1190,38 @@ namespace Barotrauma { if (GameMain.Server != null) { - + if (!IsDead) + { + if (memInput.Count > 0) + { + AnimController.Frozen = false; + prevDequeuedInput = dequeuedInput; + dequeuedInput = memInput[memInput.Count - 1]; + memInput.RemoveAt(memInput.Count - 1); + } + else + { + AnimController.Frozen = true; + return; + } + } + } + } + else + { + if (GameMain.Client != null) + { + byte newInput = 0; + newInput |= IsKeyDown(InputType.Left) ? (byte)1 : (byte)0; + newInput |= IsKeyDown(InputType.Right) ? (byte)2 : (byte)0; + newInput |= IsKeyDown(InputType.Up) ? (byte)4 : (byte)0; + newInput |= IsKeyDown(InputType.Down) ? (byte)8 : (byte)0; + memInput.Insert(0,newInput); + LastNetworkUpdateID++; + while (memInput.Count>60) + { + memInput.RemoveAt(memInput.Count - 1); + } } } /*if (networkUpdateSent) @@ -1585,12 +1660,48 @@ namespace Barotrauma } public virtual void ClientWrite(NetOutgoingMessage msg) - { - //TODO: write inputs + { + if (GameMain.Server != null) return; + msg.Write((byte)ClientNetObject.CHARACTER_INPUT); + + while (memInput.Count > 60) + { + memInput.RemoveAt(memInput.Count - 1); + } + + msg.Write(LastNetworkUpdateID); + byte inputCount = Math.Min((byte)memInput.Count, (byte)60); + msg.Write(inputCount); + + for (int i = 0; i < inputCount; i++) + { + msg.Write(memInput[i]); + } } public virtual void ServerRead(NetIncomingMessage msg, Client c) - { - //TODO: read inputs + { + if (GameMain.Server == null) return; + + UInt32 networkUpdateID = msg.ReadUInt32(); + byte inputCount = msg.ReadByte(); + + for (int i=0;i LastNetworkUpdateID) + { + LastNetworkUpdateID = networkUpdateID; + } + while (memInput.Count > 60) + { + //deleting inputs from the queue here means the server is way behind and data needs to be dropped + memInput.RemoveAt(memInput.Count - 1); + } } public virtual void ServerWrite(NetOutgoingMessage msg, Client c) diff --git a/Subsurface/Source/Networking/Client.cs b/Subsurface/Source/Networking/Client.cs index 49d5fb350..32670c87d 100644 --- a/Subsurface/Source/Networking/Client.cs +++ b/Subsurface/Source/Networking/Client.cs @@ -28,7 +28,7 @@ namespace Barotrauma.Networking public NetConnection Connection { get; set; } public string version; public bool inGame; - public UInt32 lastRecvLobbyUpdate = 0; + public UInt32 lastRecvGeneralUpdate = 0; public bool hasLobbyData = false; public UInt32 lastSentChatMsgID = 0; //last msg this client said diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 6a9888cd4..6d217b0c6 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -485,6 +485,10 @@ namespace Barotrauma.Networking { SendLobbyUpdate(); } + else + { + SendIngameUpdate(); + } // Update current time updateTimer = DateTime.Now + updateInterval; @@ -513,6 +517,9 @@ namespace Barotrauma.Networking case ServerPacketHeader.UPDATE_LOBBY: ReadLobbyUpdate(inc); break; + case ServerPacketHeader.UPDATE_INGAME: + ReadIngameUpdate(inc); + break; case ServerPacketHeader.QUERY_STARTGAME: string subName = inc.ReadString(); string subHash = inc.ReadString(); @@ -560,6 +567,9 @@ namespace Barotrauma.Networking bool respawnAllowed = inc.ReadBoolean(); + float posX = inc.ReadFloat(); + float posY = inc.ReadFloat(); + GameModePreset gameMode = GameModePreset.list.Find(gm => gm.Name == modeName); if (gameMode == null) @@ -591,6 +601,10 @@ namespace Barotrauma.Networking GameMain.GameScreen.Select(); + DebugConsole.NewMessage(Convert.ToString(posX) + "," + Convert.ToString(posY), Color.Lime); + Character myChar = Character.Create(Character.HumanConfigFile, new Vector2(posX, posY), null, true, false); + Character.Controlled = myChar; + yield return CoroutineStatus.Success; } @@ -661,6 +675,32 @@ namespace Barotrauma.Networking } } + private void ReadIngameUpdate(NetIncomingMessage inc) + { + ServerNetObject objHeader; + while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE) + { + switch (objHeader) + { + case ServerNetObject.SYNC_IDS: + lastSentChatMsgID = inc.ReadUInt32(); + break; + case ServerNetObject.CHARACTER_POSITION: + bool dead = inc.ReadBoolean(); + inc.ReadPadBits(); + if (Character.Controlled != null) + { + if (dead && !Character.Controlled.IsDead) + Character.Controlled.Kill(CauseOfDeath.Damage); + } + break; + case ServerNetObject.CHAT_MESSAGE: + ChatMessage.ClientRead(inc); + break; + } + } + } + private void SendLobbyUpdate() { NetOutgoingMessage outmsg = client.CreateMessage(); @@ -683,6 +723,34 @@ namespace Barotrauma.Networking client.SendMessage(outmsg, NetDeliveryMethod.Unreliable); } + private void SendIngameUpdate() + { + NetOutgoingMessage outmsg = client.CreateMessage(); + outmsg.Write((byte)ClientPacketHeader.UPDATE_INGAME); + + outmsg.Write((byte)ClientNetObject.SYNC_IDS); + outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID); + outmsg.Write(ChatMessage.LastID); + ChatMessage removeMsg; + while ((removeMsg = chatMsgQueue.Find(cMsg => cMsg.NetStateID <= lastSentChatMsgID)) != null) + { + chatMsgQueue.Remove(removeMsg); + } + + foreach (ChatMessage cMsg in chatMsgQueue) + { + cMsg.ClientWrite(outmsg); + } + + if (Character.Controlled != null) + { + Character.Controlled.ClientWrite(outmsg); + } + + outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE); + client.SendMessage(outmsg, NetDeliveryMethod.Unreliable); + } + public override void SendChatMessage(string message, ChatMessageType? type = null) { if (client.ServerConnection == null) return; diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index 3b74f5881..6c62be45b 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -505,7 +505,7 @@ namespace Barotrauma.Networking ClientReadLobby(inc); break; case ClientPacketHeader.UPDATE_INGAME: - //TODO + ClientReadIngame(inc); break; } } @@ -558,7 +558,7 @@ namespace Barotrauma.Networking inc.SenderConnection.Disconnect("You're not a connected client."); return; } - + ClientNetObject objHeader; while ((objHeader=(ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE) { @@ -566,10 +566,10 @@ namespace Barotrauma.Networking { case ClientNetObject.SYNC_IDS: //TODO: might want to use a clever class for this - UInt32 lastLobbyUpdID = inc.ReadUInt32(); - if (lastLobbyUpdID > c.lastRecvLobbyUpdate) + UInt32 lastGeneralUpdID = inc.ReadUInt32(); + if (lastGeneralUpdID > c.lastRecvGeneralUpdate) { - c.lastRecvLobbyUpdate = lastLobbyUpdID; + c.lastRecvGeneralUpdate = lastGeneralUpdID; } UInt32 lastChatID = inc.ReadUInt32(); if (lastChatID > c.lastRecvChatMsgID) @@ -580,6 +580,9 @@ namespace Barotrauma.Networking case ClientNetObject.CHAT_MESSAGE: ChatMessage.ServerRead(inc, c); break; + default: + return; + //break; } } @@ -593,18 +596,78 @@ namespace Barotrauma.Networking inc.SenderConnection.Disconnect("You're not a connected client."); return; } + + ClientNetObject objHeader; + while ((objHeader = (ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE) + { + switch (objHeader) + { + case ClientNetObject.SYNC_IDS: + //TODO: might want to use a clever class for this + UInt32 lastGeneralUpdID = inc.ReadUInt32(); + if (lastGeneralUpdID > c.lastRecvGeneralUpdate) + { + c.lastRecvGeneralUpdate = lastGeneralUpdID; + } + UInt32 lastChatID = inc.ReadUInt32(); + if (lastChatID > c.lastRecvChatMsgID) + { + c.lastRecvChatMsgID = lastChatID; + } + break; + case ClientNetObject.CHAT_MESSAGE: + ChatMessage.ServerRead(inc, c); + break; + case ClientNetObject.CHARACTER_INPUT: + if (c.Character != null && !c.Character.IsDead && !c.Character.IsUnconscious) + { + c.Character.ServerRead(inc, c); + } + break; + default: + return; + //break; + } + } } private void ClientWriteIngame(Client c) { + NetOutgoingMessage outmsg = server.CreateMessage(); + outmsg.Write((byte)ServerPacketHeader.UPDATE_INGAME); + + outmsg.Write((byte)ServerNetObject.SYNC_IDS); + outmsg.Write(c.lastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server + + foreach (GUIComponent gc in GameMain.NetLobbyScreen.ChatBox.children) + { + if (gc is GUITextBlock) + { + if (gc.UserData is ChatMessage) + { + ChatMessage cMsg = (ChatMessage)gc.UserData; + if (cMsg.NetStateID > c.lastRecvChatMsgID) + { + cMsg.ServerWrite(outmsg, c); + } + } + } + } + + outmsg.Write((byte)ServerNetObject.CHARACTER_POSITION); if (c.Character != null && !c.Character.IsDead) { - + outmsg.Write(false); //not dead + outmsg.WritePadBits(); } else { - + outmsg.Write(true); //dead + outmsg.WritePadBits(); } + + outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE); + server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable); } private void ClientWriteLobby(Client c) @@ -614,7 +677,7 @@ namespace Barotrauma.Networking outmsg.Write((byte)ServerNetObject.SYNC_IDS); - if (c.lastRecvLobbyUpdate c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0); + List characterInfos = new List(); + foreach (Client c in connectedClients) + { + c.inGame = true; + + WayPoint spawnPoint = WayPoint.GetRandom(SpawnType.Human); + Vector2 spawnPosition = spawnPoint.WorldPosition; + + DebugConsole.NewMessage(Convert.ToString(spawnPosition.X) + "," + Convert.ToString(spawnPosition.Y), Color.Lime); + Character spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition, null, true, false); + c.Character = spawnedCharacter; + + GameMain.GameSession.CrewManager.characters.Add(c.Character); + } + + CreateStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset, connectedClients); + //var startMessage = CreateStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset); + //server.SendMessage(startMessage, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0); yield return CoroutineStatus.Running; @@ -802,28 +881,34 @@ namespace Barotrauma.Networking yield return CoroutineStatus.Success; } - private NetOutgoingMessage CreateStartMessage(int seed, Submarine selectedSub, GameModePreset selectedMode) + private void CreateStartMessage(int seed, Submarine selectedSub, GameModePreset selectedMode, List clients) { - NetOutgoingMessage msg = server.CreateMessage(); - msg.Write((byte)ServerPacketHeader.STARTGAME); + foreach (Client c in clients) + { + NetOutgoingMessage msg = server.CreateMessage(); + msg.Write((byte)ServerPacketHeader.STARTGAME); - msg.Write(seed); + msg.Write(seed); - msg.Write(GameMain.NetLobbyScreen.LevelSeed); + msg.Write(GameMain.NetLobbyScreen.LevelSeed); - msg.Write((byte)GameMain.NetLobbyScreen.MissionTypeIndex); + msg.Write((byte)GameMain.NetLobbyScreen.MissionTypeIndex); - msg.Write(selectedSub.Name); - msg.Write(selectedSub.MD5Hash.Hash); + msg.Write(selectedSub.Name); + msg.Write(selectedSub.MD5Hash.Hash); - msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name); - msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.Hash); + msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name); + msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.Hash); - msg.Write(selectedMode.Name); + msg.Write(selectedMode.Name); - msg.Write(AllowRespawn); - - return msg; + msg.Write(AllowRespawn); + + msg.Write(c.Character.WorldPosition.X); + msg.Write(c.Character.WorldPosition.Y); + + c.Connection.SendMessage(msg, NetDeliveryMethod.ReliableUnordered,0); + } } public void EndGame() diff --git a/Subsurface/Source/Networking/NetworkMember.cs b/Subsurface/Source/Networking/NetworkMember.cs index 26db014d9..9776ec2ed 100644 --- a/Subsurface/Source/Networking/NetworkMember.cs +++ b/Subsurface/Source/Networking/NetworkMember.cs @@ -14,7 +14,7 @@ namespace Barotrauma.Networking REQUEST_AUTH, //ask the server if a password is needed, if so we'll get nonce for encryption REQUEST_INIT, //ask the server to give you initialization UPDATE_LOBBY, //update state in lobby - UPDATE_INGAME, //update state ingame while alive + UPDATE_INGAME, //update state ingame RESPONSE_STARTGAME //tell the server whether you're ready to start } @@ -33,7 +33,7 @@ namespace Barotrauma.Networking AUTH_RESPONSE, //tell the player if they require a password to log in AUTH_FAILURE, //the server won't authorize player yet, however connection is still alive UPDATE_LOBBY, //update state in lobby (votes and chat messages) - UPDATE_INGAME, //update state ingame while alive (character input and chat messages) + UPDATE_INGAME, //update state ingame (character input and chat messages) QUERY_STARTGAME, //ask the clients whether they're ready to start STARTGAME //start a new round