diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs index ee2740938..764f758da 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs @@ -224,21 +224,27 @@ namespace Barotrauma.Items.Components } //Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero. - protected void StartDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime) + protected void StartDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime, bool waitForMidRoundSync = false) { if (delayedCorrectionCoroutine != null) CoroutineManager.StopCoroutines(delayedCorrectionCoroutine); - delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime)); + delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync)); } - private IEnumerable DoDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime) + private IEnumerable DoDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime, bool waitForMidRoundSync) { - while (correctionTimer > 0.0f) + while (GameMain.Client != null && + (correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing))) { correctionTimer -= CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } + if (item.Removed || GameMain.Client == null) + { + yield return CoroutineStatus.Success; + } + ((IServerSerializable)this).ClientRead(type, buffer, sendingTime); correctionTimer = 0.0f; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs index b2f10ae90..c25397641 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs @@ -1,5 +1,9 @@ using Barotrauma.Networking; +using Lidgren.Network; using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; namespace Barotrauma.Items.Components { @@ -24,5 +28,66 @@ namespace Barotrauma.Items.Components HighlightedWire = null; Connection.DrawConnections(spriteBatch, this, character); } + + + public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) + { + if (GameMain.Client.MidRoundSyncing) + { + //delay reading the state until midround syncing is done + //because some of the wires connected to the panel may not exist yet + int bitsToRead = Connections.Count * Connection.MaxLinked * 16; + StartDelayedCorrection(type, msg.ExtractBits(bitsToRead), sendingTime, waitForMidRoundSync: true); + } + else + { + ApplyRemoteState(msg); + } + } + + private void ApplyRemoteState(NetBuffer msg) + { + List prevWires = Connections.SelectMany(c => Array.FindAll(c.Wires, w => w != null)).ToList(); + List newWires = new List(); + + foreach (Connection connection in Connections) + { + connection.ClearConnections(); + } + + foreach (Connection connection in Connections) + { + for (int i = 0; i < Connection.MaxLinked; i++) + { + ushort wireId = msg.ReadUInt16(); + + Item wireItem = Entity.FindEntityByID(wireId) as Item; + if (wireItem == null) continue; + + Wire wireComponent = wireItem.GetComponent(); + if (wireComponent == null) continue; + + newWires.Add(wireComponent); + + connection.Wires[i] = wireComponent; + wireComponent.Connect(connection, false); + } + } + + foreach (Wire wire in prevWires) + { + if (wire.Connections[0] == null && wire.Connections[1] == null) + { + wire.Item.Drop(null); + } + //wires that are not in anyone's inventory (i.e. not currently being rewired) can never be connected to only one connection + // -> someone must have dropped the wire from the connection panel + else if (wire.Item.ParentInventory == null && + (wire.Connections[0] != null ^ wire.Connections[1] != null)) + { + wire.Item.Drop(null); + } + } + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs index 3141ee613..fdd20abf1 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs @@ -148,8 +148,10 @@ namespace Barotrauma.Items.Components public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) { UInt16 projectileID = msg.ReadUInt16(); - Item projectile = Entity.FindEntityByID(projectileID) as Item; + //projectile removed, do nothing + if (projectileID == 0) return; + Item projectile = Entity.FindEntityByID(projectileID) as Item; if (projectile == null) { DebugConsole.ThrowError("Failed to launch a projectile - item with the ID \"" + projectileID + " not found"); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index 7b1028a69..06a11e43c 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -1,4 +1,6 @@ using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Lidgren.Network; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; @@ -464,5 +466,69 @@ namespace Barotrauma item.Sprite.Draw(spriteBatch, new Vector2(rect.X + rect.Width / 2, rect.Y + rect.Height / 2), item.GetSpriteColor()); } + + public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) + { + receivedItemIDs = new ushort[capacity]; + + for (int i = 0; i < capacity; i++) + { + receivedItemIDs[i] = msg.ReadUInt16(); + } + + //delay applying the new state if less than 1 second has passed since this client last sent a state to the server + //prevents the inventory from briefly reverting to an old state if items are moved around in quick succession + + //also delay if we're still midround syncing, some of the items in the inventory may not exist yet + if (syncItemsDelay > 0.0f || GameMain.Client.MidRoundSyncing) + { + if (syncItemsCoroutine != null) CoroutineManager.StopCoroutines(syncItemsCoroutine); + syncItemsCoroutine = CoroutineManager.StartCoroutine(SyncItemsAfterDelay()); + } + else + { + ApplyReceivedState(); + } + } + + private IEnumerable SyncItemsAfterDelay() + { + while (syncItemsDelay > 0.0f && GameMain.Client != null && GameMain.Client.MidRoundSyncing) + { + syncItemsDelay -= CoroutineManager.DeltaTime; + yield return CoroutineStatus.Running; + } + + if (Owner.Removed || GameMain.Client == null) + { + yield return CoroutineStatus.Success; + } + + ApplyReceivedState(); + + yield return CoroutineStatus.Success; + } + + private void ApplyReceivedState() + { + if (receivedItemIDs == null) return; + + for (int i = 0; i < capacity; i++) + { + if (receivedItemIDs[i] == 0) + { + if (Items[i] != null) Items[i].Drop(); + } + else + { + var item = Entity.FindEntityByID(receivedItemIDs[i]) as Item; + if (item == null) continue; + + TryPutItem(item, i, true, true, null, false); + } + } + + receivedItemIDs = null; + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 07829eb16..55f6d0feb 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -64,6 +64,11 @@ namespace Barotrauma.Networking { get { return fileReceiver; } } + + public bool MidRoundSyncing + { + get { return entityEventManager.MidRoundSyncing; } + } public GameClient(string newName) { diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index 90e02538e..79cfc308b 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -23,6 +23,11 @@ namespace Barotrauma.Networking private UInt16 lastReceivedID; + public bool MidRoundSyncing + { + get { return firstNewID.HasValue; } + } + public ClientEntityEventManager(GameClient client) { events = new List(); @@ -195,13 +200,19 @@ namespace Barotrauma.Networking catch (Exception e) { + string errorMsg = "Failed to read event for entity \"" + entity.ToString() + "\"! (MidRoundSyncing: " + thisClient.MidRoundSyncing + ")\n" + e.StackTrace; + errorMsg += "\nPrevious entities:"; + for (int j = entities.Count - 2; j >= 0; j--) + { + errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString()); + } + if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e); } GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to read event for entity \"" + entity.ToString() + "\"!\n" + e.StackTrace); + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); msg.Position = msgPosition + msgLength * 8; } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs index 7cffc8836..159372397 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs @@ -150,11 +150,9 @@ namespace Barotrauma.Items.Components { foreach (Connection connection in Connections) { - Wire[] wires = Array.FindAll(connection.Wires, w => w != null); - msg.WriteRangedInteger(0, Connection.MaxLinked, wires.Length); - for (int i = 0; i < wires.Length; i++) + for (int i = 0; i < Connection.MaxLinked; i++) { - msg.Write(wires[i].Item.ID); + msg.Write(connection.Wires[i]?.Item == null ? (ushort)0 : connection.Wires[i].Item.ID); } } } @@ -295,52 +293,6 @@ namespace Barotrauma.Items.Components public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { ClientWrite(msg, extraData); - } - - public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) - { - List prevWires = Connections.SelectMany(c => Array.FindAll(c.Wires, w => w != null)).ToList(); - List newWires = new List(); - - foreach (Connection connection in Connections) - { - connection.ClearConnections(); - } - - foreach (Connection connection in Connections) - { - int wireCount = msg.ReadRangedInteger(0, Connection.MaxLinked); - for (int i = 0; i < wireCount; i++) - { - ushort wireId = msg.ReadUInt16(); - - Item wireItem = Entity.FindEntityByID(wireId) as Item; - if (wireItem == null) continue; - - Wire wireComponent = wireItem.GetComponent(); - if (wireComponent == null) continue; - - newWires.Add(wireComponent); - - connection.Wires[i] = wireComponent; - wireComponent.Connect(connection, false); - } - } - - foreach (Wire wire in prevWires) - { - if (wire.Connections[0] == null && wire.Connections[1] == null) - { - wire.Item.Drop(null); - } - //wires that are not in anyone's inventory (i.e. not currently being rewired) can never be connected to only one connection - // -> someone must have dropped the wire from the connection panel - else if (wire.Item.ParentInventory == null && - (wire.Connections[0] != null ^ wire.Connections[1] != null)) - { - wire.Item.Drop(null); - } - } - } + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index 7ad0320a4..0c622bbc4 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs @@ -419,8 +419,8 @@ namespace Barotrauma.Items.Components public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { - //ID of the launched projectile - msg.Write(((Item)extraData[2]).ID); + Item item = (Item)extraData[2]; + msg.Write(item.Removed ? (ushort)0 : item.ID); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index 43a9a8efa..c315aa67a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -269,63 +269,5 @@ namespace Barotrauma msg.Write((ushort)(Items[i] == null ? 0 : Items[i].ID)); } } - - public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) - { - receivedItemIDs = new ushort[capacity]; - - for (int i = 0; i < capacity; i++) - { - receivedItemIDs[i] = msg.ReadUInt16(); - } - - if (syncItemsDelay > 0.0f) - { - //delay applying the new state if less than 1 second has passed since this client last sent a state to the server - //prevents the inventory from briefly reverting to an old state if items are moved around in quick succession - if (syncItemsCoroutine != null) CoroutineManager.StopCoroutines(syncItemsCoroutine); - - syncItemsCoroutine = CoroutineManager.StartCoroutine(SyncItemsAfterDelay()); - } - else - { - ApplyReceivedState(); - } - } - - private IEnumerable SyncItemsAfterDelay() - { - while (syncItemsDelay > 0.0f) - { - syncItemsDelay -= CoroutineManager.DeltaTime; - yield return CoroutineStatus.Running; - } - - ApplyReceivedState(); - - yield return CoroutineStatus.Success; - } - - private void ApplyReceivedState() - { - if (receivedItemIDs == null) return; - - for (int i = 0; i < capacity; i++) - { - if (receivedItemIDs[i] == 0) - { - if (Items[i] != null) Items[i].Drop(); - } - else - { - var item = Entity.FindEntityByID(receivedItemIDs[i]) as Item; - if (item == null) continue; - - TryPutItem(item, i, true, true, null, false); - } - } - - receivedItemIDs = null; - } } }