diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index 7e4c3bedf..83ced404d 100644 --- a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.9.5.1")] -[assembly: AssemblyFileVersion("0.9.5.1")] +[assembly: AssemblyVersion("0.9.6.0")] +[assembly: AssemblyFileVersion("0.9.6.0")] diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index c28135e75..5f67d9a2d 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -85,8 +85,7 @@ namespace Barotrauma { if (character.Inventory != null) { - if (!character.LockHands && character.Stun < 0.1f && - (character.SelectedConstruction == null || character.SelectedConstruction?.GetComponent()?.User != character)) + if (!LockInventory(character)) { character.Inventory.Update(deltaTime, cam); } @@ -325,7 +324,7 @@ namespace Barotrauma } if (character.Inventory != null && !character.LockHands) { - character.Inventory.Locked = (character.SelectedConstruction?.GetComponent()?.User == character); + character.Inventory.Locked = LockInventory(character); character.Inventory.DrawOwn(spriteBatch); character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ? CharacterInventory.Layout.Default : @@ -368,6 +367,17 @@ namespace Barotrauma } } + private static bool LockInventory(Character character) + { + if (character?.Inventory == null || !character.AllowInput || character.LockHands) { return true; } + + //lock if using a controller, except if we're also using a connection panel in the same item + return + character.SelectedConstruction != null && + character.SelectedConstruction?.GetComponent()?.User == character && + character.SelectedConstruction?.GetComponent()?.User != character; + } + private static void DrawOrderIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Order order, float iconAlpha = 1.0f) { if (order.TargetAllCharacters) diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 4acb4a66a..494a5a698 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -2041,6 +2041,16 @@ namespace Barotrauma commands.Add(new Command("spawnsub", "spawnsub [subname]: Spawn a submarine at the position of the cursor", (string[] args) => { + if (GameMain.NetworkMember != null) + { + ThrowError("Cannot spawn additional submarines during a multiplayer session."); + return; + } + if (args.Length == 0) + { + ThrowError("Please enter the name of the submarine."); + return; + } try { Submarine spawnedSub = Submarine.Load(args[0], false); diff --git a/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs index b93ad97c1..9cf4343e8 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs @@ -220,6 +220,7 @@ namespace Barotrauma msgHolder.RectTransform.Resize(new Point(msgHolder.Rect.Width, msgHolder.Children.Sum(c => c.Rect.Height) + (int)(10 * GUI.Scale)), resizeChildren: false); msgHolder.RectTransform.SizeChanged += Recalculate; chatBox.RecalculateChildren(); + chatBox.UpdateScrollBarSize(); } CoroutineManager.StartCoroutine(UpdateMessageAnimation(msgHolder, 0.5f)); diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs index 586663afe..985468b71 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs @@ -435,12 +435,30 @@ namespace Barotrauma public void SelectNext(bool force = false, bool autoScroll = true) { - Select(Math.Min(Content.CountChildren - 1, SelectedIndex + 1), force, autoScroll); + int index = SelectedIndex + 1; + while (index < Content.CountChildren) + { + if (Content.GetChild(index).Visible) + { + Select(index, force, autoScroll); + break; + } + index++; + } } public void SelectPrevious(bool force = false, bool autoScroll = true) { - Select(Math.Max(0, SelectedIndex - 1), force, autoScroll); + int index = SelectedIndex - 1; + while (index >= 0) + { + if (Content.GetChild(index).Visible) + { + Select(index, force, autoScroll); + break; + } + index--; + } } public void Select(int childIndex, bool force = false, bool autoScroll = true) diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUINumberInput.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUINumberInput.cs index b456de7ec..9c0318c8d 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUINumberInput.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUINumberInput.cs @@ -233,7 +233,7 @@ namespace Barotrauma switch (InputType) { case NumberType.Int: - TextBox.textFilterFunction = text => new string(text.Where(c => char.IsNumber(c)).ToArray()); + TextBox.textFilterFunction = text => new string(text.Where(c => char.IsNumber(c) || c == '-').ToArray()); break; case NumberType.Float: TextBox.textFilterFunction = text => new string(text.Where(c => char.IsDigit(c) || c == '.' || c == '-').ToArray()); diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUITickBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUITickBox.cs index 5d0c06360..423f5809d 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUITickBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUITickBox.cs @@ -126,6 +126,11 @@ namespace Barotrauma set { text.Text = value; } } + public Color? DefaultTextColor + { + get { return defaultTextColor; } + } + public GUITickBox(RectTransform rectT, string label, ScalableFont font = null, string style = "") : base(null, rectT) { CanBeFocused = true; diff --git a/Barotrauma/BarotraumaClient/Source/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index fff5b55ee..e3d56e2b5 100644 --- a/Barotrauma/BarotraumaClient/Source/GameMain.cs +++ b/Barotrauma/BarotraumaClient/Source/GameMain.cs @@ -318,6 +318,8 @@ namespace Barotrauma private void InitUserStats() { + return; + if (GameSettings.ShowUserStatisticsPrompt) { if (TextManager.ContainsTag("statisticspromptheader") && TextManager.ContainsTag("statisticsprompttext")) @@ -404,14 +406,18 @@ namespace Barotrauma GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice); DebugConsole.Init(); - if (Config.AutoUpdateWorkshopItems) + CrossThread.RequestExecutionOnMainThread(() => { - if (SteamManager.AutoUpdateWorkshopItems()) + if (Config.AutoUpdateWorkshopItems) { - ContentPackage.LoadAll(); - Config.ReloadContentPackages(); + if (SteamManager.AutoUpdateWorkshopItems()) + { + ContentPackage.LoadAll(); + Config.ReloadContentPackages(); + } } - } + }); + if (SelectedPackages.None()) { @@ -844,6 +850,8 @@ namespace Barotrauma SteamManager.Update((float)Timing.Step); + TaskPool.Update(); + SoundManager?.Update(); Timing.Accumulator -= Timing.Step; diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index dd7f32d52..b67e07466 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -431,7 +431,6 @@ namespace Barotrauma } else { - characterArea.CanBeFocused = false; characterArea.CanBeSelected = false; } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs index 73c7dd7a0..6ee85c0b8 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs @@ -204,9 +204,14 @@ namespace Barotrauma { campaign.SuppressStateSending = true; - campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex); - campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); - campaign.Map.SelectMission(selectedMissionIndex); + //we need to have the latest save file to display location/mission/store + if (campaign.LastSaveID == saveID) + { + campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex); + campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); + campaign.Map.SelectMission(selectedMissionIndex); + campaign.CargoManager.SetPurchasedItems(purchasedItems); + } campaign.startWatchmanID = startWatchmanID; campaign.endWatchmanID = endWatchmanID; @@ -215,7 +220,6 @@ namespace Barotrauma campaign.PurchasedHullRepairs = purchasedHullRepairs; campaign.PurchasedItemRepairs = purchasedItemRepairs; campaign.PurchasedLostShuttles = purchasedLostShuttles; - campaign.CargoManager.SetPurchasedItems(purchasedItems); if (myCharacterInfo != null) { diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs index 0fa67f1c0..47411eddc 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs @@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components if (brokenSprite == null) { //broken doors turn black if no broken sprite has been configured - color = color * (item.Condition / item.Prefab.Health); + color *= (item.Condition / item.Prefab.Health); color.A = 255; } @@ -184,7 +184,7 @@ namespace Barotrauma.Items.Components partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen) { - if (isStuck || + if ((IsStuck && !isNetworkMessage) || (PredictedState == null && isOpen == open) || (PredictedState != null && isOpen == PredictedState.Value && isOpen == open)) { @@ -210,11 +210,7 @@ namespace Barotrauma.Items.Components StopPicking(null); PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse, item.WorldPosition); } - } - - //opening a partially stuck door makes it less stuck - if (isOpen) stuck = MathHelper.Clamp(stuck - 30.0f, 0.0f, 100.0f); - + } } public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) @@ -225,7 +221,7 @@ namespace Barotrauma.Items.Components bool forcedOpen = msg.ReadBoolean(); SetState(open, isNetworkMessage: true, sendNetworkMessage: false, forcedOpen: forcedOpen); Stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8); - + if (isStuck) { OpenState = 0.0f; } PredictedState = null; } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/LightComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/LightComponent.cs index 891eb7036..67ce9cba6 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/LightComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/LightComponent.cs @@ -17,7 +17,20 @@ namespace Barotrauma.Items.Components { get { return light; } } - + + public override void OnScaleChanged() + { + light.SpriteScale = Vector2.One * item.Scale; + light.Position = ParentBody != null ? ParentBody.Position : item.Position; + } + + partial void SetLightSourceState(bool enabled, float brightness) + { + if (light == null) { return; } + light.Enabled = enabled; + light.Color = LightColor * brightness; + } + public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) { if (light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f) @@ -28,7 +41,7 @@ namespace Barotrauma.Items.Components public override void FlipX(bool relativeToSub) { - if (light?.LightSprite != null && item.Prefab.CanSpriteFlipX) + if (light?.LightSprite != null && item.Prefab.CanSpriteFlipX && item.body == null) { light.LightSpriteEffect = light.LightSpriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None; @@ -39,5 +52,11 @@ namespace Barotrauma.Items.Components { IsOn = msg.ReadBoolean(); } + + protected override void RemoveComponentSpecific() + { + base.RemoveComponentSpecific(); + light.Remove(); + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs index f6ffb5e1d..7e5080ef7 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs @@ -77,6 +77,26 @@ namespace Barotrauma.Items.Components public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) { state = msg.ReadBoolean(); + ushort userID = msg.ReadUInt16(); + if (userID == 0) + { + if (user != null) + { + IsActive = false; + CancelUsing(user); + user = null; + } + } + else + { + Character newUser = Entity.FindEntityByID(userID) as Character; + if (newUser != user) + { + CancelUsing(user); + } + user = newUser; + IsActive = true; + } } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Power/PowerTransfer.cs index 78abdac83..599748a21 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Power/PowerTransfer.cs @@ -23,18 +23,23 @@ namespace Barotrauma.Items.Components { Enabled = false }; + powerIndicator.TextColor = powerIndicator.DefaultTextColor.Value; + highVoltageIndicator = new GUITickBox(new RectTransform(indicatorSize, paddedFrame.RectTransform) { AbsoluteOffset = new Point(0, (int)(40 * GUI.yScale)) }, TextManager.Get("PowerTransferHighVoltage"), style: "IndicatorLightRed") { ToolTip = TextManager.Get("PowerTransferTipOvervoltage"), Enabled = false }; + highVoltageIndicator.TextColor = highVoltageIndicator.DefaultTextColor.Value; + lowVoltageIndicator = new GUITickBox(new RectTransform(indicatorSize, paddedFrame.RectTransform) { AbsoluteOffset = new Point(0, (int)(80 * GUI.yScale)) }, TextManager.Get("PowerTransferLowVoltage"), style: "IndicatorLightRed") { ToolTip = TextManager.Get("PowerTransferTipLowvoltage"), Enabled = false }; + lowVoltageIndicator.TextColor = lowVoltageIndicator.DefaultTextColor.Value; var textContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform, Anchor.TopRight)); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs index 4925339d3..30ec31a46 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs @@ -142,7 +142,7 @@ namespace Barotrauma.Items.Components { if (repairSoundChannel == null || !repairSoundChannel.IsPlaying) { - repairSoundChannel = SoundPlayer.PlaySound("repair", item.WorldPosition, hullGuess: item.CurrentHull); + repairSoundChannel = SoundPlayer.PlaySound("repair", item.WorldPosition, hullGuess: item.CurrentHull); } } else @@ -221,8 +221,17 @@ namespace Barotrauma.Items.Components deteriorationTimer = msg.ReadSingle(); deteriorateAlwaysResetTimer = msg.ReadSingle(); DeteriorateAlways = msg.ReadBoolean(); - CurrentFixer = msg.ReadBoolean() ? Character.Controlled : null; + ushort currentFixerID = msg.ReadUInt16(); currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); + + if (currentFixerID == 0) + { + CurrentFixer = null; + } + else + { + CurrentFixer = Entity.FindEntityByID(currentFixerID) as Character; + } } public void ClientWrite(IWriteMessage msg, object[] extraData = null) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs index ba26adbd4..6c2853e44 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs @@ -119,6 +119,7 @@ namespace Barotrauma.Items.Components { DrawWire(spriteBatch, draggingConnected, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height - 10), null, panel, ""); } + panel.TriggerRewiringSound(); if (!PlayerInput.LeftButtonHeld()) { @@ -129,7 +130,11 @@ namespace Barotrauma.Items.Components panel.DisconnectedWires.Add(draggingConnected); } - if (GameMain.Client != null) { panel.Item.CreateClientEvent(panel); } + if (GameMain.Client != null) + { + panel.Item.CreateClientEvent(panel); + } + draggingConnected = null; } } @@ -205,7 +210,10 @@ namespace Barotrauma.Items.Components SetWire(index, draggingConnected); } } - if (GameMain.Client != null) { panel.Item.CreateClientEvent(panel); } + if (GameMain.Client != null) + { + panel.Item.CreateClientEvent(panel); + } draggingConnected = null; } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs index d66e716d3..b50d9e2a5 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs @@ -11,19 +11,28 @@ namespace Barotrauma.Items.Components { partial class ConnectionPanel : ItemComponent, IServerSerializable, IClientSerializable { + //how long the rewiring sound plays after doing changes to the wiring + const float RewireSoundDuration = 5.0f; + public static Wire HighlightedWire; private SoundChannel rewireSoundChannel; + private float rewireSoundTimer; partial void InitProjSpecific(XElement element) { - if (GuiFrame == null) return; + if (GuiFrame == null) { return; } new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null) { UserData = this }; } + public void TriggerRewiringSound() + { + rewireSoundTimer = RewireSoundDuration; + } + partial void UpdateProjSpecific(float deltaTime) { foreach (Wire wire in DisconnectedWires) @@ -40,7 +49,9 @@ namespace Barotrauma.Items.Components } } } - if (user != null && user.SelectedConstruction == item && HasRequiredItems(user, addMessage: false)) + + rewireSoundTimer -= deltaTime; + if (user != null && user.SelectedConstruction == item && rewireSoundTimer > 0.0f) { if (rewireSoundChannel == null || !rewireSoundChannel.IsPlaying) { @@ -51,12 +62,13 @@ namespace Barotrauma.Items.Components { rewireSoundChannel?.FadeOutAndDispose(); rewireSoundChannel = null; + rewireSoundTimer = 0.0f; } } public override void Move(Vector2 amount) { - if (item.Submarine == null || item.Submarine.Loading || Screen.Selected != GameMain.SubEditorScreen) return; + if (item.Submarine == null || item.Submarine.Loading || Screen.Selected != GameMain.SubEditorScreen) { return; } MoveConnectedWires(amount); } @@ -103,6 +115,7 @@ namespace Barotrauma.Items.Components //delay reading the state until midround syncing is done //because some of the wires connected to the panel may not exist yet long msgStartPos = msg.BitPosition; + msg.ReadUInt16(); //user ID foreach (Connection connection in Connections) { for (int i = 0; i < Connection.MaxLinked; i++) @@ -121,6 +134,8 @@ namespace Barotrauma.Items.Components } else { + //don't trigger rewiring sounds if the rewiring is being done by the local user (in that case we'll trigger it locally) + if (Character.Controlled == null || user != Character.Controlled) { TriggerRewiringSound(); } ApplyRemoteState(msg); } } @@ -130,6 +145,17 @@ namespace Barotrauma.Items.Components List prevWires = Connections.SelectMany(c => c.Wires.Where(w => w != null)).ToList(); List newWires = new List(); + ushort userID = msg.ReadUInt16(); + + if (userID == 0) + { + user = null; + } + else + { + user = Entity.FindEntityByID(userID) as Character; + } + foreach (Connection connection in Connections) { connection.ClearConnections(); diff --git a/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs b/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs index 30dcd6401..e771573f7 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/DockingPort.cs @@ -145,6 +145,21 @@ namespace Barotrauma.Items.Components } Dock(DockingTarget); + if (joint == null) + { + string errorMsg = "Error while reading a docking port network event (Dock method did not create a joint between the ports)." + + " Submarine: " + (item.Submarine?.Name ?? "null") + + ", target submarine: " + (DockingTarget.item.Submarine?.Name ?? "null"); + if (item.Submarine?.DockedTo.Contains(DockingTarget.item.Submarine) ?? false) + { + errorMsg += "\nAlready docked."; + } + if (item.Submarine == DockingTarget.item.Submarine) + { + errorMsg += "\nTrying to dock the submarine to itself."; + } + GameAnalyticsManager.AddErrorEventOnce("DockingPort.ClientRead:JointNotCreated", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + } if (isLocked) { diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index 7af86c154..d5e0e01fb 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -916,21 +916,30 @@ namespace Barotrauma case NetEntityEvent.Type.ApplyStatusEffect: { ActionType actionType = (ActionType)msg.ReadRangedInteger(0, Enum.GetValues(typeof(ActionType)).Length - 1); - byte componentIndex = msg.ReadByte(); - ushort targetID = msg.ReadUInt16(); - byte targetLimbID = msg.ReadByte(); + byte componentIndex = msg.ReadByte(); + ushort targetCharacterID = msg.ReadUInt16(); + byte targetLimbID = msg.ReadByte(); + ushort useTargetID = msg.ReadUInt16(); + Vector2? worldPosition = null; + bool hasPosition = msg.ReadBoolean(); + if (hasPosition) + { + worldPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle()); + } ItemComponent targetComponent = componentIndex < components.Count ? components[componentIndex] : null; - Character target = FindEntityByID(targetID) as Character; - Limb targetLimb = target != null && targetLimbID < target.AnimController.Limbs.Length ? target.AnimController.Limbs[targetLimbID] : null; - + Character targetCharacter = FindEntityByID(targetCharacterID) as Character; + Limb targetLimb = targetCharacter != null && targetLimbID < targetCharacter.AnimController.Limbs.Length ? + targetCharacter.AnimController.Limbs[targetLimbID] : null; + Entity useTarget = FindEntityByID(useTargetID); + if (targetComponent == null) { - ApplyStatusEffects(actionType, 1.0f, target, targetLimb, true); + ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, true, worldPosition: worldPosition); } else { - targetComponent.ApplyStatusEffects(actionType, 1.0f, target, targetLimb); + targetComponent.ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, worldPosition: worldPosition); } } break; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs index 40acaf548..898e0fda3 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs @@ -228,12 +228,12 @@ namespace Barotrauma.Lights activeLights.Clear(); foreach (LightSource light in lights) { - if (light.Color.A < 1 || light.Range < 1.0f || !light.Enabled) continue; - if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, light.Range, viewRect)) continue; + if (!light.Enabled) { continue; } + if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; } + if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, light.LightSourceParams.TextureRange, viewRect)) { continue; } activeLights.Add(light); } - //clear the lightmap graphics.Clear(Color.Black); graphics.BlendState = BlendState.Additive; @@ -244,9 +244,9 @@ namespace Barotrauma.Lights spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform); foreach (LightSource light in activeLights) { - if (!light.IsBackground) continue; + if (!light.IsBackground) { continue; } light.DrawSprite(spriteBatch, cam); - light.DrawLightVolume(spriteBatch, lightEffect, transform); + if (light.Color.A > 0 && light.Range > 0.0f) { light.DrawLightVolume(spriteBatch, lightEffect, transform); } backgroundSpritesDrawn = true; } GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive); @@ -288,7 +288,7 @@ namespace Barotrauma.Lights spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform); foreach (LightSource light in activeLights) { - if (light.IsBackground) continue; + if (light.IsBackground) { continue; } light.DrawSprite(spriteBatch, cam); } spriteBatch.End(); @@ -303,6 +303,8 @@ namespace Barotrauma.Lights //draw characters to obstruct the highlighted items/characters and light sprites //--------------------------------------------------------------------------------------------------- + SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"]; + SolidColorEffect.Parameters["color"].SetValue(Color.Black.ToVector4()); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, effect: SolidColorEffect, transformMatrix: spriteBatchTransform); foreach (Character character in Character.CharacterList) { @@ -347,8 +349,8 @@ namespace Barotrauma.Lights foreach (LightSource light in activeLights) { - if (light.IsBackground) continue; - light.DrawLightVolume(spriteBatch, lightEffect, transform); + if (light.IsBackground) { continue; } + if (light.Color.A > 0 && light.Range > 0.0f) { light.DrawLightVolume(spriteBatch, lightEffect, transform); } } Vector3 offset = Vector3.Zero;// new Vector3(Submarine.MainSub.DrawPosition.X, Submarine.MainSub.DrawPosition.Y, 0.0f); lightEffect.World = Matrix.CreateTranslation(Vector3.Zero) * transform; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightSource.cs b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightSource.cs index eadd2a93c..35537b08d 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightSource.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightSource.cs @@ -31,10 +31,22 @@ namespace Barotrauma.Lights get { return range; } set { - range = MathHelper.Clamp(value, 0.0f, 2048.0f); + TextureRange = range; + if (OverrideLightTexture != null) + { + TextureRange += Math.Max( + Math.Abs(OverrideLightTexture.RelativeOrigin.X - 0.5f) * OverrideLightTexture.size.X, + Math.Abs(OverrideLightTexture.RelativeOrigin.Y - 0.5f) * OverrideLightTexture.size.Y); + } } } + + public float TextureRange + { + get; + private set; + } public Sprite OverrideLightTexture { @@ -89,6 +101,8 @@ namespace Barotrauma.Lights break; case "lighttexture": OverrideLightTexture = new Sprite(subElement, preMultiplyAlpha: false); + //refresh TextureRange + Range = range; break; } } @@ -115,8 +129,16 @@ namespace Barotrauma.Lights class LightSource { + //how many pixels the position of the light needs to change for the light volume to be recalculated + const float MovementRecalculationThreshold = 10.0f; + //how many radians the light needs to rotate for the light volume to be recalculated + const float RotationRecalculationThreshold = 0.02f; + private static Texture2D lightTexture; + private VertexPositionColorTexture[] vertices; + private short[] indices; + private List hullsInRange; public Texture2D texture; @@ -167,6 +189,9 @@ namespace Barotrauma.Lights private int vertexCount; private int indexCount; + private Vector2 translateVertices; + private float rotateVertices; + private readonly LightSourceParams lightSourceParams; public LightSourceParams LightSourceParams => lightSourceParams; @@ -177,26 +202,38 @@ namespace Barotrauma.Lights get { return position; } set { - if (Math.Abs(position.X - value.X) < 0.1f && Math.Abs(position.Y - value.Y) < 0.1f) return; + Vector2 moveAmount = value - position; + if (Math.Abs(moveAmount.X) < 0.1f && Math.Abs(moveAmount.Y) < 0.1f) { return; } position = value; - if (Vector2.DistanceSquared(prevCalculatedPosition, position) < 5.0f * 5.0f) return; + //translate light volume manually instead of doing a full recalculation when moving by a small amount + if (Vector2.DistanceSquared(prevCalculatedPosition, position) < MovementRecalculationThreshold * MovementRecalculationThreshold && vertices != null) + { + translateVertices = position - prevCalculatedPosition; + return; + } NeedsHullCheck = true; NeedsRecalculation = true; - prevCalculatedPosition = position; } } + private float prevCalculatedRotation; private float rotation; public float Rotation { get { return rotation; } set { - if (Math.Abs(rotation - value) < 0.01f) return; + if (Math.Abs(value - rotation) < 0.001f) { return; } rotation = value; + if (Math.Abs(rotation - prevCalculatedRotation) < RotationRecalculationThreshold && vertices != null) + { + rotateVertices = rotation - prevCalculatedRotation; + return; + } + NeedsHullCheck = true; NeedsRecalculation = true; } @@ -711,16 +748,25 @@ namespace Barotrauma.Lights return retVal; } + private void CalculateLightVertices(List rayCastHits) { - List vertices = new List(); + vertexCount = rayCastHits.Count * 2 + 1; + indexCount = (rayCastHits.Count) * 9; + + //recreate arrays if they're too small or excessively large + if (vertices == null || vertices.Length < vertexCount || vertices.Length > vertexCount * 3) + { + vertices = new VertexPositionColorTexture[vertexCount]; + indices = new short[indexCount]; + } Vector2 drawPos = position; - if (ParentSub != null) drawPos += ParentSub.DrawPosition; + if (ParentSub != null) { drawPos += ParentSub.DrawPosition; } float cosAngle = (float)Math.Cos(Rotation); float sinAngle = -(float)Math.Sin(Rotation); - + Vector2 uvOffset = Vector2.Zero; Vector2 overrideTextureDims = Vector2.One; if (OverrideLightTexture != null) @@ -728,15 +774,15 @@ namespace Barotrauma.Lights overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); Vector2 origin = OverrideLightTexture.Origin; - if (LightSpriteEffect == SpriteEffects.FlipHorizontally) origin.X = OverrideLightTexture.SourceRect.Width - origin.X; - if (LightSpriteEffect == SpriteEffects.FlipVertically) origin.Y = (OverrideLightTexture.SourceRect.Height - origin.Y); + if (LightSpriteEffect == SpriteEffects.FlipHorizontally) { origin.X = OverrideLightTexture.SourceRect.Width - origin.X; } + if (LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = OverrideLightTexture.SourceRect.Height - origin.Y; } uvOffset = (origin / overrideTextureDims) - new Vector2(0.5f, 0.5f); } // Add a vertex for the center of the mesh - vertices.Add(new VertexPositionColorTexture(new Vector3(position.X, position.Y, 0), - Color.White, new Vector2(0.5f, 0.5f) + uvOffset)); - + vertices[0] = new VertexPositionColorTexture(new Vector3(position.X, position.Y, 0), + Color.White, GetUV(new Vector2(0.5f, 0.5f) + uvOffset, LightSpriteEffect)); + //hacky fix to exc excessively large light volumes (they used to be up to 4x the range of the light if there was nothing to block the rays). //might want to tweak the raycast logic in a way that this isn't necessary float boundRadius = Range * 1.1f / (1.0f - Math.Max(Math.Abs(uvOffset.X), Math.Abs(uvOffset.Y))); @@ -800,68 +846,88 @@ namespace Barotrauma.Lights //finally, create the vertices VertexPositionColorTexture fullVert = new VertexPositionColorTexture(new Vector3(position.X + rawDiff.X, position.Y + rawDiff.Y, 0), - Color.White, new Vector2(0.5f, 0.5f) + diff); + Color.White, GetUV(new Vector2(0.5f, 0.5f) + diff, LightSpriteEffect)); VertexPositionColorTexture fadeVert = new VertexPositionColorTexture(new Vector3(position.X + rawDiff.X + nDiff.X, position.Y + rawDiff.Y + nDiff.Y, 0), - Color.White * 0.0f, new Vector2(0.5f, 0.5f) + diff); + Color.White * 0.0f, GetUV(new Vector2(0.5f, 0.5f) + diff, LightSpriteEffect)); - vertices.Add(fullVert); - vertices.Add(fadeVert); + vertices[1 + i * 2] = fullVert; + vertices[1 + i * 2 + 1] = fadeVert; } // Compute the indices to form triangles - List indices = new List(); - for (int i = 0; i < rayCastHits.Count-1; i++) + for (int i = 0; i < rayCastHits.Count - 1; i++) { //main light body - indices.Add(0); - indices.Add((short)((i*2 + 3) % vertices.Count)); - indices.Add((short)((i*2 + 1) % vertices.Count)); - + indices[i * 9] = 0; + indices[i * 9 + 1] = (short)((i * 2 + 3) % vertexCount); + indices[i * 9 + 2] = (short)((i * 2 + 1) % vertexCount); + //faded light - indices.Add((short)((i*2 + 1) % vertices.Count)); - indices.Add((short)((i*2 + 3) % vertices.Count)); - indices.Add((short)((i*2 + 4) % vertices.Count)); + indices[i * 9 + 3] = (short)((i * 2 + 1) % vertexCount); + indices[i * 9 + 4] = (short)((i * 2 + 3) % vertexCount); + indices[i * 9 + 5] = (short)((i * 2 + 4) % vertexCount); - indices.Add((short)((i*2 + 2) % vertices.Count)); - indices.Add((short)((i*2 + 1) % vertices.Count)); - indices.Add((short)((i*2 + 4) % vertices.Count)); + indices[i * 9 + 6] = (short)((i * 2 + 2) % vertexCount); + indices[i * 9 + 7] = (short)((i * 2 + 1) % vertexCount); + indices[i * 9 + 8] = (short)((i * 2 + 4) % vertexCount); } - + //main light body - indices.Add(0); - indices.Add((short)(1)); - indices.Add((short)(vertices.Count - 2)); - + indices[(rayCastHits.Count - 1) * 9] = 0; + indices[(rayCastHits.Count - 1) * 9 + 1] = (short)(1); + indices[(rayCastHits.Count - 1) * 9 + 2] = (short)(vertexCount - 2); + //faded light - indices.Add((short)(1)); - indices.Add((short)(vertices.Count-1)); - indices.Add((short)(vertices.Count-2)); + indices[(rayCastHits.Count - 1) * 9 + 3] = (short)(1); + indices[(rayCastHits.Count - 1) * 9 + 4] = (short)(vertexCount - 1); + indices[(rayCastHits.Count - 1) * 9 + 5] = (short)(vertexCount - 2); - indices.Add((short)(1)); - indices.Add((short)(2)); - indices.Add((short)(vertices.Count-1)); - - vertexCount = vertices.Count; - indexCount = indices.Count; + indices[(rayCastHits.Count - 1) * 9 + 6] = (short)(1); + indices[(rayCastHits.Count - 1) * 9 + 7] = (short)(2); + indices[(rayCastHits.Count - 1) * 9 + 8] = (short)(vertexCount - 1); //TODO: a better way to determine the size of the vertex buffer and handle changes in size? //now we just create a buffer for 64 verts and make it larger if needed if (lightVolumeBuffer == null) { - lightVolumeBuffer = new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, Math.Max(64, (int)(vertexCount*1.5)), BufferUsage.None); - lightVolumeIndexBuffer = new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(short), Math.Max(64*3, (int)(indexCount * 1.5)), BufferUsage.None); + lightVolumeBuffer = new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, Math.Max(64, (int)(vertexCount * 1.5)), BufferUsage.None); + lightVolumeIndexBuffer = new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(short), Math.Max(64 * 3, (int)(indexCount * 1.5)), BufferUsage.None); } else if (vertexCount > lightVolumeBuffer.VertexCount || indexCount > lightVolumeIndexBuffer.IndexCount) { lightVolumeBuffer.Dispose(); lightVolumeIndexBuffer.Dispose(); - lightVolumeBuffer = new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, (int)(vertexCount*1.5), BufferUsage.None); + lightVolumeBuffer = new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, (int)(vertexCount * 1.5), BufferUsage.None); lightVolumeIndexBuffer = new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(short), (int)(indexCount * 1.5), BufferUsage.None); } - - lightVolumeBuffer.SetData(vertices.ToArray()); - lightVolumeIndexBuffer.SetData(indices.ToArray()); + + lightVolumeBuffer.SetData(vertices, 0, vertexCount); + lightVolumeIndexBuffer.SetData(indices, 0, indexCount); + + Vector2 GetUV(Vector2 vert, SpriteEffects effects) + { + if (effects == SpriteEffects.FlipHorizontally) + { + vert.X = 1.0f - vert.X; + } + else if (effects == SpriteEffects.FlipVertically) + { + vert.Y = 1.0f - vert.Y; + } + else if (effects == (SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically)) + { + vert.X = 1.0f - vert.X; + vert.Y = 1.0f - vert.Y; + } + vert.Y = 1.0f - vert.Y; + return vert; + } + + translateVertices = Vector2.Zero; + rotateVertices = 0.0f; + prevCalculatedPosition = position; + prevCalculatedRotation = rotation; } /// @@ -944,15 +1010,12 @@ namespace Barotrauma.Lights Vector2 drawPos = position; if (ParentSub != null) drawPos += ParentSub.DrawPosition; - drawPos.Y = -drawPos.Y; + drawPos.Y = -drawPos.Y; spriteBatch.Draw(currentTexture, drawPos, null, Color, -rotation, center, scale, SpriteEffects.None, 1); return; } - Vector3 offset = ParentSub == null ? - Vector3.Zero : new Vector3(ParentSub.DrawPosition.X, ParentSub.DrawPosition.Y, 0.0f); - lightEffect.World = Matrix.CreateTranslation(offset) * transform; if (NeedsRecalculation) { @@ -962,8 +1025,16 @@ namespace Barotrauma.Lights lastRecalculationTime = (float)Timing.TotalTime; NeedsRecalculation = false; } - - if (vertexCount == 0) return; + + + Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition; + lightEffect.World = + Matrix.CreateTranslation(-new Vector3(position, 0.0f)) * + Matrix.CreateRotationZ(rotateVertices) * + Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) * + transform; + + if (vertexCount == 0) { return; } lightEffect.DiffuseColor = (new Vector3(Color.R, Color.G, Color.B) * (Color.A / 255.0f)) / 255.0f; if (OverrideLightTexture != null) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 43c3b637f..0b085b3bc 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -483,6 +483,8 @@ namespace Barotrauma.Networking if (DateTime.Now > timeOut) { clientPeer?.Close(Lidgren.Network.NetConnection.NoResponseMessage); + var msgBox = new GUIMessageBox(TextManager.Get("ConnectionFailed"), TextManager.Get("CouldNotConnectToServer")); + msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu; reconnectBox?.Close(); reconnectBox = null; break; } @@ -1232,7 +1234,9 @@ namespace Barotrauma.Networking if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal) { - string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ")."; + string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed: " + Level.Loaded.Seed + + ", sub: " + Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash.ShortHash + ")" + + ", mirrored: " + Level.Loaded.Mirrored + ")."; DebugConsole.ThrowError(errorMsg, createMessageBox: true); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + levelSeed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); CoroutineManager.StartCoroutine(EndGame("")); @@ -1274,6 +1278,7 @@ namespace Barotrauma.Networking gameStarted = false; Character.Controlled = null; + SpawnAsTraitor = false; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.LightManager.LosEnabled = false; respawnManager = null; @@ -1432,6 +1437,8 @@ namespace Barotrauma.Networking } } + private bool initialUpdateReceived; + private void ReadLobbyUpdate(IReadMessage inc) { ServerNetObject objHeader; @@ -1452,13 +1459,15 @@ namespace Barotrauma.Networking UInt16 settingsLen = inc.ReadUInt16(); byte[] settingsData = inc.ReadBytes(settingsLen); - if (inc.ReadBoolean()) + bool isInitialUpdate = inc.ReadBoolean(); + if (isInitialUpdate) { if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("Received initial lobby update, ID: " + updateID + ", last ID: " + GameMain.NetLobbyScreen.LastUpdateID, Color.Gray); } ReadInitialUpdate(inc); + initialUpdateReceived = true; } string selectSubName = inc.ReadString(); @@ -1489,7 +1498,9 @@ namespace Barotrauma.Networking float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f; //ignore the message if we already a more up-to-date one - if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID)) + //or if we're still waiting for the initial update + if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID) && + (isInitialUpdate || initialUpdateReceived)) { ReadWriteMessage settingsBuf = new ReadWriteMessage(); settingsBuf.Write(settingsData, 0, settingsLen); settingsBuf.BitPosition = 0; @@ -2248,12 +2259,10 @@ namespace Barotrauma.Networking { if (gameStarted) { - tickBox.Visible = false; + tickBox.Parent.Visible = false; return false; } - Vote(VoteType.StartRound, tickBox.Selected); - return true; } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index 92ddf7a01..1832c8c3a 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -200,6 +200,7 @@ namespace Barotrauma.Networking } msg.BitPosition += msgLength * 8; + msg.ReadPadBits(); } else { @@ -213,6 +214,20 @@ namespace Barotrauma.Networking try { ReadEvent(msg, entity, sendingTime); + msg.ReadPadBits(); + + if (msg.BitPosition != msgPosition + msgLength * 8) + { + string errorMsg = "Message byte position incorrect after reading an event for the entity \"" + entity.ToString() + + "\". Read " + (msg.BitPosition - msgPosition) + " bits, expected message length was " + (msgLength * 8) + " bits."; +#if DEBUG + DebugConsole.ThrowError(errorMsg); +#endif + GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + + //TODO: force the BitPosition to correct place? Having some entity in a potentially incorrect state is not as bad as a desync kick + //msg.BitPosition = (int)(msgPosition + msgLength * 8); + } } catch (Exception e) @@ -231,9 +246,9 @@ namespace Barotrauma.Networking GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); msg.BitPosition = (int)(msgPosition + msgLength * 8); + msg.ReadPadBits(); } } - msg.ReadPadBits(); } return true; } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/Primitives/Peers/SteamP2PClientPeer.cs b/Barotrauma/BarotraumaClient/Source/Networking/Primitives/Peers/SteamP2PClientPeer.cs index 7e6736a07..31471c56f 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/Primitives/Peers/SteamP2PClientPeer.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/Primitives/Peers/SteamP2PClientPeer.cs @@ -275,6 +275,7 @@ namespace Barotrauma.Networking #if DEBUG CoroutineManager.InvokeAfter(() => { + if (GameMain.Client == null) { return; } if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Facepunch.Steamworks.Networking.SendType.Reliable) { return; } int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1; for (int i = 0; i < count; i++) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs b/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs index 33beeb8f4..e6b6757ae 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs @@ -18,7 +18,17 @@ namespace Barotrauma.Networking public UInt64 OwnerID; public bool OwnerVerified; - public string ServerName; + private string serverName; + public string ServerName + { + get { return serverName; } + set + { + serverName = value; + if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); } + } + } + public string ServerMessage; public bool GameStarted; public int PlayerCount; @@ -455,38 +465,7 @@ namespace Barotrauma.Networking if (SteamFriend.IsPlayingThisGame && SteamFriend.ServerLobbyId != 0) { LobbyID = SteamFriend.ServerLobbyId; - SteamManager.Instance.LobbyList.SetManualLobbyDataCallback(LobbyID, (lobby) => - { - SteamManager.Instance.LobbyList.SetManualLobbyDataCallback(LobbyID, null); - - if (string.IsNullOrWhiteSpace(lobby.GetData("haspassword"))) { return; } - bool.TryParse(lobby.GetData("haspassword"), out bool hasPassword); - int.TryParse(lobby.GetData("playercount"), out int currPlayers); - int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers); - //UInt64.TryParse(lobby.GetData("connectsteamid"), out ulong connectSteamId); - string ip = lobby.GetData("hostipaddress"); - UInt64 ownerId = SteamManager.SteamIDStringToUInt64(lobby.GetData("lobbyowner")); - - if (OwnerID != ownerId) { return; } - - if (string.IsNullOrWhiteSpace(ip)) { ip = ""; } - - ServerName = lobby.Name; - Port = ""; - QueryPort = ""; - IP = ip; - PlayerCount = currPlayers; - MaxPlayers = maxPlayers; - HasPassword = hasPassword; - RespondedToSteamQuery = true; - LobbyID = lobby.LobbyID; - OwnerID = ownerId; - PingChecked = false; - OwnerVerified = true; - SteamManager.AssignLobbyDataToServerInfo(lobby, this); - - onServerRulesReceived?.Invoke(this); - }); + SteamManager.Instance.LobbyList.RequestLobbyData(LobbyID); } else diff --git a/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs index f34de749d..d49057f4f 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs @@ -326,8 +326,8 @@ namespace Barotrauma.Steam localQuery.OnFinished = onFinished; #endif - instance.client.LobbyList.OnLobbiesUpdated = () => { UpdateLobbyQuery(onServerFound, onServerRulesReceived, onFinished); }; - instance.client.LobbyList.Refresh(); + + instance.client.LobbyList.Request(); return true; } @@ -382,42 +382,6 @@ namespace Barotrauma.Steam return true; } - private static void UpdateLobbyQuery(Action onServerFound, Action onServerRulesReceived, Action onFinished) - { - foreach (LobbyList.Lobby lobby in instance.client.LobbyList.Lobbies) - { - if (string.IsNullOrWhiteSpace(lobby.GetData("haspassword"))) { continue; } - bool.TryParse(lobby.GetData("haspassword"), out bool hasPassword); - int.TryParse(lobby.GetData("playercount"), out int currPlayers); - int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers); - UInt64 ownerId = SteamIDStringToUInt64(lobby.GetData("lobbyowner")); - //UInt64.TryParse(lobby.GetData("connectsteamid"), out ulong connectSteamId); - string ip = lobby.GetData("hostipaddress"); - if (string.IsNullOrWhiteSpace(ip)) { ip = ""; } - - var serverInfo = new ServerInfo() - { - ServerName = lobby.Name, - Port = "", - QueryPort = "", - IP = ip, - PlayerCount = currPlayers, - MaxPlayers = maxPlayers, - HasPassword = hasPassword, - RespondedToSteamQuery = true, - LobbyID = lobby.LobbyID, - OwnerID = ownerId - }; - serverInfo.PingChecked = false; - AssignLobbyDataToServerInfo(lobby, serverInfo); - - onServerFound(serverInfo); - //onServerRulesReceived(serverInfo); - } - - onFinished(); - } - public static void AssignLobbyDataToServerInfo(LobbyList.Lobby lobby, ServerInfo serverInfo) { serverInfo.ServerMessage = lobby.GetData("message"); @@ -495,6 +459,7 @@ namespace Barotrauma.Steam serverInfo.PingChecked = true; serverInfo.Ping = s.Ping; serverInfo.LobbyID = 0; + serverInfo.OwnerVerified = true; if (responded) { s.FetchRules(); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs index e9da97687..ba7ba712d 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs @@ -248,8 +248,10 @@ namespace Barotrauma MapEntityCategory newCategory = (MapEntityCategory)userdata; if (newCategory != selectedItemCategory) { - searchBox.Text = ""; + searchBox.Text = ""; + storeItemList.ScrollBar.BarScroll = 0f; } + FilterStoreItems((MapEntityCategory)userdata, searchBox.Text); return true; } @@ -947,7 +949,9 @@ namespace Barotrauma var itemFrame = myItemList.Content.GetChildByUserData(pi); if (itemFrame == null) { - itemFrame = CreateItemFrame(pi, pi.ItemPrefab.GetPrice(Campaign.Map.CurrentLocation), myItemList); + var priceInfo = pi.ItemPrefab.GetPrice(Campaign.Map.CurrentLocation); + if (priceInfo == null) { continue; } + itemFrame = CreateItemFrame(pi, priceInfo, myItemList); } itemFrame.GetChild(0).GetChild().IntValue = pi.Quantity; existingItemFrames.Add(itemFrame); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditor/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditor/CharacterEditorScreen.cs index 873606255..7ae72d924 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditor/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditor/CharacterEditorScreen.cs @@ -99,6 +99,7 @@ namespace Barotrauma.CharacterEditor private Rectangle spriteSheetRect; private Rectangle CalculateSpritesheetRectangle() => + Textures == null || Textures.None() ? Rectangle.Empty : new Rectangle( spriteSheetOffsetX, spriteSheetOffsetY, @@ -656,12 +657,6 @@ namespace Barotrauma.CharacterEditor } if (!isFrozen) { - if (character.AnimController.Invalid) - { - Reset(new Character[] { character }); - SpawnCharacter(currentCharacterConfig); - } - Submarine.MainSub.SetPrevTransform(Submarine.MainSub.Position); Submarine.MainSub.Update((float)deltaTime); @@ -722,6 +717,7 @@ namespace Barotrauma.CharacterEditor foreach (Limb limb in character.AnimController.Limbs) { if (limb == null || limb.ActiveSprite == null) { continue; } + if (selectedJoints.Any(j => j.LimbA == limb || j.LimbB == limb)) { continue; } // Select limbs on ragdoll if (editLimbs && !spriteSheetRect.Contains(PlayerInput.MousePosition) && MathUtils.RectangleContainsPoint(GetLimbPhysicRect(limb), PlayerInput.MousePosition)) { @@ -2727,11 +2723,19 @@ namespace Barotrauma.CharacterEditor return false; } #endif - character.Params.Save(); - GUI.AddMessage(GetCharacterEditorTranslation("CharacterSavedTo").Replace("[path]", CharacterParams.FullPath), Color.Green, font: GUI.Font, lifeTime: 5); - character.AnimController.SaveRagdoll(); - GUI.AddMessage(GetCharacterEditorTranslation("RagdollSavedTo").Replace("[path]", RagdollParams.FullPath), Color.Green, font: GUI.Font, lifeTime: 5); - AnimParams.ForEach(p => p.Save()); + if (!string.IsNullOrEmpty(RagdollParams.Texture) && !File.Exists(RagdollParams.Texture)) + { + DebugConsole.ThrowError($"Invalid texture path: {RagdollParams.Texture}"); + return false; + } + else + { + character.Params.Save(); + GUI.AddMessage(GetCharacterEditorTranslation("CharacterSavedTo").Replace("[path]", CharacterParams.FullPath), Color.Green, font: GUI.Font, lifeTime: 5); + character.AnimController.SaveRagdoll(); + GUI.AddMessage(GetCharacterEditorTranslation("RagdollSavedTo").Replace("[path]", RagdollParams.FullPath), Color.Green, font: GUI.Font, lifeTime: 5); + AnimParams.ForEach(p => p.Save()); + } return true; }; // Spacing diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditor/Wizard.cs b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditor/Wizard.cs index b14c551f4..3d1871334 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditor/Wizard.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditor/Wizard.cs @@ -40,6 +40,10 @@ namespace Barotrauma.CharacterEditor canEnterSubmarine = ragdoll.CanEnterSubmarine; canWalk = ragdoll.CanWalk; texturePath = ragdoll.Texture; + if (string.IsNullOrEmpty(texturePath) && !name.Equals(Character.HumanSpeciesName, StringComparison.OrdinalIgnoreCase)) + { + texturePath = ragdoll.Limbs.FirstOrDefault()?.GetSprite().Texture; + } } public static Wizard instance; @@ -265,8 +269,30 @@ namespace Barotrauma.CharacterEditor }; if (ofd.ShowDialog() == DialogResult.OK) { + string file = ofd.FileName; + string relativePath = UpdaterUtil.GetRelativePath(Path.GetFullPath(file), Environment.CurrentDirectory); + string destinationPath = relativePath; + + //copy file to XML path if it's not located relative to the game's files + if (relativePath.StartsWith("..") || + Path.GetPathRoot(Environment.CurrentDirectory) != Path.GetPathRoot(file)) + { + destinationPath = Path.Combine(Path.GetDirectoryName(XMLPath), Path.GetFileName(file)); + + string destinationDir = Path.GetDirectoryName(destinationPath); + if (!Directory.Exists(destinationDir)) + { + Directory.CreateDirectory(destinationDir); + } + + if (!File.Exists(destinationPath)) + { + File.Copy(file, Path.GetFullPath(destinationPath), overwrite: true); + } + } + isTextureSelected = true; - texturePathElement.Text = ToolBox.ConvertAbsoluteToRelativePath(ofd.FileName); + texturePathElement.Text = destinationPath; } return true; } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index c41608a1c..d52b67ba0 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -8,6 +8,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using RestSharp; using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -84,7 +85,7 @@ namespace Barotrauma } } #else - FetchRemoteContent(Frame.RectTransform); + FetchRemoteContent(); #endif @@ -753,12 +754,20 @@ namespace Barotrauma exeName = "DedicatedServer.exe"; } - string arguments = "-name \"" + name.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"" + + string arguments = "-name \"" + ToolBox.EscapeCharacters(name) + "\"" + " -public " + isPublicBox.Selected.ToString() + " -playstyle " + ((PlayStyle)playstyleBanner.UserData).ToString() + - " -password \"" + passwordBox.Text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"" + " -maxplayers " + maxPlayersBox.Text; + if (!string.IsNullOrWhiteSpace(passwordBox.Text)) + { + arguments += " -password \"" + ToolBox.EscapeCharacters(passwordBox.Text) + "\""; + } + else + { + arguments += " -nopassword"; + } + int ownerKey = 0; if (Steam.SteamManager.GetSteamID()!=0) @@ -774,6 +783,7 @@ namespace Barotrauma string filename = exeName; #if LINUX || OSX filename = "./" + Path.GetFileNameWithoutExtension(exeName); + arguments = ToolBox.EscapeCharacters(arguments); #endif var processInfo = new ProcessStartInfo { @@ -1149,34 +1159,15 @@ namespace Barotrauma } #endregion - private void FetchRemoteContent(RectTransform parent) + private void FetchRemoteContent() { if (string.IsNullOrEmpty(GameMain.Config.RemoteContentUrl)) { return; } try { var client = new RestClient(GameMain.Config.RemoteContentUrl); var request = new RestRequest("MenuContent.xml", Method.GET); - - IRestResponse response = client.Execute(request); - if (response.ResponseStatus != ResponseStatus.Completed) - { - return; - } - if (response.StatusCode != HttpStatusCode.OK) - { - return; - } - - string xml = response.Content; - int index = xml.IndexOf('<'); - if (index > 0) { xml = xml.Substring(index, xml.Length - index); } - if (string.IsNullOrWhiteSpace(xml)) { return; } - - XElement element = XDocument.Parse(xml)?.Root; - foreach (XElement subElement in element.Elements()) - { - GUIComponent.FromXML(subElement, parent); - } + client.ExecuteAsync(request, RemoteContentReceived); + CoroutineManager.StartCoroutine(WairForRemoteContentReceived()); } catch (Exception e) @@ -1189,5 +1180,60 @@ namespace Barotrauma return; } } + + private IEnumerable WairForRemoteContentReceived() + { + while (true) + { + lock (remoteContentLock) + { + if (remoteContentResponse != null) { break; } + } + yield return new WaitForSeconds(0.1f); + } + lock (remoteContentLock) + { + if (remoteContentResponse.ResponseStatus != ResponseStatus.Completed || remoteContentResponse.StatusCode != HttpStatusCode.OK) + { + yield return CoroutineStatus.Success; + } + + try + { + string xml = remoteContentResponse.Content; + int index = xml.IndexOf('<'); + if (index > 0) { xml = xml.Substring(index, xml.Length - index); } + if (!string.IsNullOrWhiteSpace(xml)) + { + XElement element = XDocument.Parse(xml)?.Root; + foreach (XElement subElement in element.Elements()) + { + GUIComponent.FromXML(subElement, Frame.RectTransform); + } + } + } + + catch (Exception e) + { +#if DEBUG + DebugConsole.ThrowError("Reading received remote main menu content failed.", e); +#endif + GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.WairForRemoteContentReceived:Exception", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Reading received remote main menu content failed. " + e.Message); + } + } + yield return CoroutineStatus.Success; + } + + private readonly object remoteContentLock = new object(); + private IRestResponse remoteContentResponse; + + private void RemoteContentReceived(IRestResponse response, RestRequestAsyncHandle handle) + { + lock (remoteContentLock) + { + remoteContentResponse = response; + } + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 39f80d712..932b3e09d 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -319,6 +319,14 @@ namespace Barotrauma RelativeSpacing = panelSpacing }; + GameMain.Instance.OnResolutionChanged += () => + { + if (innerFrame != null) + { + innerFrame.RectTransform.MaxSize = new Point(int.MaxValue, GameMain.GraphicsHeight - 50); + } + }; + var panelContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), innerFrame.RectTransform, Anchor.Center), isHorizontal: true) { Stretch = true, @@ -440,6 +448,14 @@ namespace Barotrauma Stretch = true }; + GameMain.Instance.OnResolutionChanged += () => + { + if (panelContainer != null && sideBar != null) + { + sideBar.RectTransform.MaxSize = new Point(650, panelContainer.RectTransform.Rect.Height); + } + }; + //player info panel ------------------------------------------------------------ myCharacterFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), sideBar.RectTransform)); @@ -1781,24 +1797,23 @@ namespace Barotrauma RelativeSpacing = 0.03f }; - var headerContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedPlayerFrame.RectTransform), isHorizontal: true) + var headerContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedPlayerFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; - var nameText = new GUITextBlock(new RectTransform(new Vector2(0.75f, 1.0f), headerContainer.RectTransform), + var nameText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), headerContainer.RectTransform), text: selectedClient.Name, font: GUI.LargeFont); + nameText.Text = ToolBox.LimitString(nameText.Text, nameText.Font, nameText.Rect.Width); if (selectedClient.SteamID != 0 && Steam.SteamManager.IsInitialized) { - var viewSteamProfileButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), headerContainer.RectTransform, Anchor.TopCenter), + var viewSteamProfileButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), headerContainer.RectTransform, Anchor.TopCenter) { MaxSize = new Point(int.MaxValue, (int)(40 * GUI.Scale)) }, TextManager.Get("ViewSteamProfile")) { UserData = selectedClient }; - - GUITextBlock.AutoScaleAndNormalize(nameText, viewSteamProfileButton.TextBlock); - + viewSteamProfileButton.TextBlock.AutoScale = true; viewSteamProfileButton.OnClicked = (bt, userdata) => { Steam.SteamManager.Instance.Overlay.OpenUrl("https://steamcommunity.com/profiles/" + selectedClient.SteamID.ToString()); @@ -2054,7 +2069,7 @@ namespace Barotrauma } var closeButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonAreaLower.RectTransform, Anchor.BottomRight), - TextManager.Get("Close")) + TextManager.Get("Close"), style: "GUIButtonLarge") { IgnoreLayoutGroups = true, OnClicked = ClosePlayerFrame @@ -2409,6 +2424,13 @@ namespace Barotrauma AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - characterInfoFrame.Rect.Width, button.Rect.Bottom) }); + characterInfoFrame.RectTransform.SizeChanged += () => + { + if (characterInfoFrame == null || HeadSelectionList?.RectTransform == null || button == null) { return; } + HeadSelectionList.RectTransform.Resize(new Point(characterInfoFrame.Rect.Width, (characterInfoFrame.Rect.Bottom - button.Rect.Bottom) + characterInfoFrame.Rect.Height * 2)); + HeadSelectionList.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - characterInfoFrame.Rect.Width, button.Rect.Bottom); + }; + new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), HeadSelectionList.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black) { UserData = "outerglow", @@ -2446,7 +2468,7 @@ namespace Barotrauma headSprite.SourceRect = new Rectangle(CharacterInfo.CalculateOffset(headSprite, head.Value.ToPoint()), headSprite.SourceRect.Size); characterSprites.Add(headSprite); - if (row == null || itemsInRow >= 4) + if (itemsInRow >= 4 || row == null || gender != (Gender)row.UserData) { row = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.333f), HeadSelectionList.Content.RectTransform), true) { @@ -2536,6 +2558,14 @@ namespace Barotrauma JobSelectionFrame = new GUIFrame(new RectTransform(frameSize, GUI.Canvas, Anchor.TopLeft) { AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - frameSize.X, characterInfoFrame.Rect.Bottom) }, "GUIFrameListBox"); + characterInfoFrame.RectTransform.SizeChanged += () => + { + if (characterInfoFrame == null || JobSelectionFrame?.RectTransform == null) { return; } + Point size = new Point(characterInfoFrame.Rect.Width, characterInfoFrame.Rect.Height * 2); + JobSelectionFrame.RectTransform.Resize(size); + JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - size.X, characterInfoFrame.Rect.Bottom); + }; + new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), JobSelectionFrame.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black) { UserData = "outerglow", @@ -2600,32 +2630,24 @@ namespace Barotrauma image.Visible = currVisible == (variantIndex + 1); } - var variantButton = new GUIButton(new RectTransform(new Vector2(0.15f), jobButton.RectTransform, scaleBasis: ScaleBasis.BothWidth) { RelativeOffset = new Vector2(0.05f, 0.05f + 0.2f * variantIndex) }, (variantIndex + 1).ToString(), style: null) + var variantButton = CreateJobVariantButton(jobPrefab, variantIndex, images.Length, jobButton); + variantButton.OnClicked = (btn, obj) => { - Color = new Color(50, 50, 50, 200), - HoverColor = Color.Gray * 0.75f, - PressedColor = Color.Black * 0.75f, - SelectedColor = new Color(45, 70, 100, 200), - UserData = new Pair(jobPrefab.First, variantIndex+1), - OnClicked = (btn, obj) => + currSelected.Selected = false; + int k = ((Pair)obj).Second; + btn.Parent.UserData = obj; + for (int j = 0; j < images.Length; j++) { - currSelected.Selected = false; - int k = ((Pair)obj).Second; - btn.Parent.UserData = obj; - for (int j = 0; j < images.Length; j++) + foreach (GUIImage image in images[j]) { - foreach (GUIImage image in images[j]) - { - image.Visible = k == (j + 1); - } + image.Visible = k == (j + 1); } - currSelected = btn; - currSelected.Selected = true; - - return false; } - }; + currSelected = btn; + currSelected.Selected = true; + return false; + }; if (currVisible == (variantIndex + 1)) { currSelected = variantButton; @@ -2818,7 +2840,7 @@ namespace Barotrauma subList.Enabled = !enabled && AllowSubSelection; shuttleList.Enabled = !enabled && GameMain.Client.HasPermission(ClientPermissions.SelectSub); - StartButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound) && GameMain.Client.GameStarted && !enabled; + StartButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound) && !GameMain.Client.GameStarted && !enabled; if (campaignViewButton != null) { campaignViewButton.Visible = enabled; } @@ -2943,34 +2965,28 @@ namespace Barotrauma } if (images.Length > 1) { - var variantButton = new GUIButton(new RectTransform(new Vector2(0.15f), slot.RectTransform, scaleBasis: ScaleBasis.BothWidth) { RelativeOffset = new Vector2(0.05f, 0.25f + 0.2f * variantIndex) }, (variantIndex + 1).ToString(), style: null) + var variantButton = CreateJobVariantButton(jobPrefab, variantIndex, images.Length, slot); + variantButton.OnClicked = (btn, obj) => { - Color = new Color(50, 50, 50, 200), - HoverColor = Color.Gray * 0.75f, - PressedColor = Color.Black * 0.75f, - SelectedColor = new Color(45, 70, 100, 200), - Selected = jobPrefab.Second == (variantIndex + 1), - UserData = new Pair(jobPrefab.First, variantIndex + 1), - OnClicked = (btn, obj) => - { - int k = ((Pair)obj).Second; - btn.Parent.UserData = obj; - UpdateJobPreferences(listBox); - return false; - } + int k = ((Pair)obj).Second; + btn.Parent.UserData = obj; + UpdateJobPreferences(listBox); + return false; }; } } //info button - new GUIButton(new RectTransform(new Vector2(0.15f), slot.RectTransform, Anchor.TopLeft, scaleBasis: ScaleBasis.BothWidth) { RelativeOffset = new Vector2(0.05f) }, style: "GUIButtonInfo") + new GUIButton(new RectTransform(new Vector2(0.2f), slot.RectTransform, Anchor.TopLeft, scaleBasis: ScaleBasis.BothHeight) + { RelativeOffset = new Vector2(0.05f) }, + style: "GUIButtonInfo") { UserData = jobPrefab.First, OnClicked = ViewJobInfo }; //remove button - new GUIButton(new RectTransform(new Vector2(0.15f), slot.RectTransform, Anchor.TopRight, scaleBasis: ScaleBasis.BothWidth) { RelativeOffset = new Vector2(0.05f) }, style: "GUICancelButton") + new GUIButton(new RectTransform(new Vector2(0.2f), slot.RectTransform, Anchor.TopRight, scaleBasis: ScaleBasis.BothHeight) { RelativeOffset = new Vector2(0.05f) }, style: "GUICancelButton") { UserData = i, OnClicked = (btn, obj) => @@ -3013,6 +3029,24 @@ namespace Barotrauma } } + private GUIButton CreateJobVariantButton(Pair jobPrefab, int variantIndex, int variantCount, GUIComponent slot) + { + float relativeHeight = Math.Min(0.7f / variantCount, 0.2f); + + var btn = new GUIButton(new RectTransform(new Vector2(relativeHeight), slot.RectTransform, scaleBasis: ScaleBasis.BothHeight) + { RelativeOffset = new Vector2(0.05f, 0.25f + relativeHeight * 1.05f * variantIndex) }, + (variantIndex + 1).ToString(), style: null) + { + Color = new Color(50, 50, 50, 200), + HoverColor = Color.Gray * 0.75f, + PressedColor = Color.Black * 0.75f, + SelectedColor = new Color(45, 70, 100, 200), + Selected = jobPrefab.Second == (variantIndex + 1), + UserData = new Pair(jobPrefab.First, variantIndex + 1), + }; + return btn; + } + public Pair FailedSelectedSub; public Pair FailedSelectedShuttle; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs index a3ca7c6f5..593b15e5e 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs @@ -13,6 +13,7 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Xml.Linq; namespace Barotrauma @@ -426,7 +427,7 @@ namespace Barotrauma ToolTip = mode.Name, Selected = true, OnSelected = (tickBox) => { FilterServers(); return true; }, - UserData = mode.Name + UserData = mode.Identifier }; gameModeTickBoxes.Add(selectionTick); filterTextList.Add(selectionTick.TextBlock); @@ -736,6 +737,7 @@ namespace Barotrauma info.PlayerCount = GameMain.Client.ConnectedClients.Count; info.PingChecked = false; info.HasPassword = serverSettings.HasPassword; + info.OwnerVerified = true; if (isInfoNew) { @@ -747,6 +749,12 @@ namespace Barotrauma public void AddToRecentServers(ServerInfo info) { + if (!string.IsNullOrEmpty(info.IP)) + { + //don't add localhost to recent servers + if (IPAddress.TryParse(info.IP, out IPAddress ip) && IPAddress.IsLoopback(ip)) { return; } + } + info.Recent = true; ServerInfo existingInfo = recentServers.Find(serverInfo => info.OwnerID == serverInfo.OwnerID && (info.OwnerID != 0 ? true : (info.IP == serverInfo.IP && info.Port == serverInfo.Port))); if (existingInfo == null) @@ -898,6 +906,11 @@ namespace Barotrauma base.Select(); SelectedTab = ServerListTab.All; RefreshServers(); + + if (SteamManager.IsInitialized && SteamManager.Instance.LobbyList != null) + { + SteamManager.Instance.LobbyList.OnLobbyDataReceived = OnLobbyDataReceived; + } } public override void Deselect() @@ -905,7 +918,7 @@ namespace Barotrauma base.Deselect(); if (SteamManager.IsInitialized && SteamManager.Instance.LobbyList != null) { - SteamManager.Instance.LobbyList.OnLobbiesUpdated = null; + SteamManager.Instance.LobbyList.OnLobbyDataReceived = null; } } @@ -947,6 +960,7 @@ namespace Barotrauma (remoteVersion != null && !NetworkMember.IsCompatible(GameMain.Version, remoteVersion)); child.Visible = + serverInfo.OwnerVerified && serverInfo.ServerName.ToLowerInvariant().Contains(searchBox.Text.ToLowerInvariant()) && (!filterSameVersion.Selected || (remoteVersion != null && NetworkMember.IsCompatible(remoteVersion, GameMain.Version))) && (!filterPassword.Selected || !serverInfo.HasPassword) && @@ -1405,7 +1419,8 @@ namespace Barotrauma { yield return new WaitForSeconds((float)(refreshDisableTimer - DateTime.Now).TotalSeconds); } - + + recentServers.Concat(favoriteServers).ForEach(si => si.OwnerVerified = false); if (GameMain.Config.UseSteamMatchmaking) { serverList.ClearChildren(); @@ -1479,7 +1494,8 @@ namespace Barotrauma PlayerCount = playerCount, MaxPlayers = maxPlayers, HasPassword = hasPassWord, - GameVersion = gameVersion + GameVersion = gameVersion, + OwnerVerified = true }; foreach (string contentPackageName in contentPackageNames.Split(',')) { @@ -1511,6 +1527,37 @@ namespace Barotrauma } } + private void OnLobbyDataReceived(Facepunch.Steamworks.LobbyList.Lobby lobby) + { + if (string.IsNullOrWhiteSpace(lobby.GetData("haspassword"))) { return; } + bool.TryParse(lobby.GetData("haspassword"), out bool hasPassword); + int.TryParse(lobby.GetData("playercount"), out int currPlayers); + int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers); + //UInt64.TryParse(lobby.GetData("connectsteamid"), out ulong connectSteamId); + string ip = lobby.GetData("hostipaddress"); + UInt64 ownerId = SteamManager.SteamIDStringToUInt64(lobby.GetData("lobbyowner")); + + ServerInfo newInfo = new ServerInfo(); + + if (string.IsNullOrWhiteSpace(ip)) { ip = ""; } + + newInfo.ServerName = lobby.Name; + newInfo.Port = ""; + newInfo.QueryPort = ""; + newInfo.IP = ip; + newInfo.PlayerCount = currPlayers; + newInfo.MaxPlayers = maxPlayers; + newInfo.HasPassword = hasPassword; + newInfo.RespondedToSteamQuery = true; + newInfo.LobbyID = lobby.LobbyID; + newInfo.OwnerID = ownerId; + newInfo.PingChecked = false; + newInfo.OwnerVerified = true; + SteamManager.AssignLobbyDataToServerInfo(lobby, newInfo); + + AddToServerList(newInfo); + } + private void AddToServerList(ServerInfo serverInfo) { var serverFrame = serverList.Content.FindChild(d => (d.UserData is ServerInfo info) && @@ -1552,7 +1599,8 @@ namespace Barotrauma if (serverInfo.OwnerVerified) { DebugConsole.NewMessage(serverInfo.OwnerID + " verified!"); - var childrenToRemove = serverList.Content.FindChildren(c => (c.UserData is ServerInfo info) && info != serverInfo && info.OwnerID == serverInfo.OwnerID).ToList(); + var childrenToRemove = serverList.Content.FindChildren(c => (c.UserData is ServerInfo info) && info != serverInfo && + (serverInfo.OwnerID != 0 ? info.OwnerID == serverInfo.OwnerID : info.IP == serverInfo.IP)).ToList(); foreach (var child in childrenToRemove) { serverList.Content.RemoveChild(child); @@ -1594,7 +1642,13 @@ namespace Barotrauma UserData = "password" }; - var serverName = new GUITextBlock(new RectTransform(new Vector2(columnRelativeWidth[2] * 1.1f, 1.0f), serverContent.RectTransform), serverInfo.ServerName, style: "GUIServerListTextBox"); + var serverName = new GUITextBlock(new RectTransform(new Vector2(columnRelativeWidth[2] * 1.1f, 1.0f), serverContent.RectTransform), +#if !DEBUG + serverInfo.ServerName, +#else + ((serverInfo.OwnerID != 0 || serverInfo.LobbyID != 0) ? "[STEAMP2P] " : "[LIDGREN] ") + serverInfo.ServerName, +#endif + style: "GUIServerListTextBox"); new GUITickBox(new RectTransform(new Vector2(columnRelativeWidth[3], 0.9f), serverContent.RectTransform, Anchor.Center), label: "") { @@ -1793,7 +1847,7 @@ namespace Barotrauma GameMain.Config.PlayerName = clientNameBox.Text; GameMain.Config.SaveNewPlayerConfig(); - CoroutineManager.StartCoroutine(ConnectToServer(endpoint, serverName)); + CoroutineManager.StartCoroutine(ConnectToServer(endpoint, serverName), "ConnectToServer"); return true; } @@ -1822,40 +1876,31 @@ namespace Barotrauma public void GetServerPing(ServerInfo serverInfo, GUITextBlock serverPingText) { - if (activePings.Contains(serverInfo.IP)) { return; } - activePings.Add(serverInfo.IP); + if (CoroutineManager.IsCoroutineRunning("ConnectToServer")) { return; } + + lock (activePings) + { + if (activePings.Contains(serverInfo.IP)) { return; } + activePings.Add(serverInfo.IP); + } serverInfo.PingChecked = false; serverInfo.Ping = -1; - var pingThread = new Thread(() => { PingServer(serverInfo, 1000); }) - { - IsBackground = true - }; - pingThread.Start(); - - CoroutineManager.StartCoroutine(UpdateServerPingText(serverInfo, serverPingText, 1000)); - } - - private IEnumerable UpdateServerPingText(ServerInfo serverInfo, GUITextBlock serverPingText, int timeOut) - { - DateTime timeOutTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: timeOut); - while (DateTime.Now < timeOutTime) - { - if (serverInfo.PingChecked) + TaskPool.Add(PingServerAsync(serverInfo?.IP, 1000), + new Tuple(serverInfo, serverPingText), + (rtt, obj) => { - if (serverInfo.Ping != -1) + var info = obj.Item1; + var text = obj.Item2; + info.Ping = rtt.Result; info.PingChecked = true; + text.TextColor = GetPingTextColor(info.Ping); + text.Text = info.Ping > -1 ? info.Ping.ToString() : "?"; + lock (activePings) { - serverPingText.TextColor = GetPingTextColor(serverInfo.Ping); - } - serverPingText.Text = serverInfo.Ping > -1 ? serverInfo.Ping.ToString() : "?"; - activePings.Remove(serverInfo.IP); - yield return CoroutineStatus.Success; - } - - yield return CoroutineStatus.Running; - } - yield return CoroutineStatus.Success; + activePings.Remove(serverInfo.IP); + } + }); } private Color GetPingTextColor(int ping) @@ -1864,18 +1909,27 @@ namespace Barotrauma return ToolBox.GradientLerp(ping / 200.0f, Color.LightGreen, Color.Yellow * 0.8f, Color.Red * 0.75f); } - public void PingServer(ServerInfo serverInfo, int timeOut) + public async Task PingServerAsync(string ip, int timeOut) { - if (string.IsNullOrWhiteSpace(serverInfo?.IP)) + await Task.Yield(); + int activePingCount = 100; + while (activePingCount > 25) { - serverInfo.PingChecked = true; - serverInfo.Ping = -1; - return; + lock (activePings) + { + activePingCount = activePings.Count; + } + await Task.Delay(25); + } + + if (string.IsNullOrWhiteSpace(ip)) + { + return -1; } long rtt = -1; IPAddress address = null; - IPAddress.TryParse(serverInfo.IP, out address); + IPAddress.TryParse(ip, out address); if (address != null) { //don't attempt to ping if the address is IPv6 and it's not supported @@ -1902,8 +1956,8 @@ namespace Barotrauma } catch (PingException ex) { - string errorMsg = "Failed to ping a server (" + serverInfo.ServerName + ", " + serverInfo.IP + ") - " + (ex?.InnerException?.Message ?? ex.Message); - GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + serverInfo.IP, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, errorMsg); + string errorMsg = "Failed to ping a server (" + ip + ") - " + (ex?.InnerException?.Message ?? ex.Message); + GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + ip, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, errorMsg); #if DEBUG DebugConsole.NewMessage(errorMsg, Color.Red); #endif @@ -1911,10 +1965,9 @@ namespace Barotrauma } } - serverInfo.PingChecked = true; - serverInfo.Ping = (int)rtt; + return (int)rtt; } - + public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) { graphics.Clear(Color.CornflowerBlue); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs index 2e108884e..20c7e2ff5 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs @@ -30,7 +30,7 @@ namespace Barotrauma private List tabButtons = new List(); - private HashSet pendingPreviewImageDownloads = new HashSet(); + private readonly HashSet pendingPreviewImageDownloads = new HashSet(); private Dictionary itemPreviewSprites = new Dictionary(); private enum Tab @@ -379,10 +379,16 @@ namespace Barotrauma if (!string.IsNullOrEmpty(item.PreviewImageUrl)) { string imagePreviewPath = Path.Combine(SteamManager.WorkshopItemPreviewImageFolder, item.Id + ".png"); - if (!pendingPreviewImageDownloads.Contains(item.PreviewImageUrl)) - { - pendingPreviewImageDownloads.Add(item.PreviewImageUrl); + bool isNewImage; + lock (pendingPreviewImageDownloads) + { + isNewImage = !pendingPreviewImageDownloads.Contains(item.PreviewImageUrl); + if (isNewImage) { pendingPreviewImageDownloads.Add(item.PreviewImageUrl); } + } + + if (isNewImage) + { if (File.Exists(imagePreviewPath)) { File.Delete(imagePreviewPath); @@ -397,10 +403,13 @@ namespace Barotrauma var request = new RestRequest(fileName, Method.GET); client.ExecuteAsync(request, response => { - pendingPreviewImageDownloads.Remove(item.PreviewImageUrl); + lock (pendingPreviewImageDownloads) + { + pendingPreviewImageDownloads.Remove(item.PreviewImageUrl); + } OnPreviewImageDownloaded(response, imagePreviewPath); CoroutineManager.StartCoroutine(WaitForItemPreviewDownloaded(item, listBox, imagePreviewPath)); - }); + }); } else { @@ -410,7 +419,10 @@ namespace Barotrauma } catch (Exception e) { - pendingPreviewImageDownloads.Remove(item.PreviewImageUrl); + lock (pendingPreviewImageDownloads) + { + pendingPreviewImageDownloads.Remove(item.PreviewImageUrl); + } DebugConsole.ThrowError("Downloading the preview image of the Workshop item \"" + TextManager.EnsureUTF8(item.Title) + "\" failed.", e); } } @@ -595,8 +607,13 @@ namespace Barotrauma private IEnumerable WaitForItemPreviewDownloaded(Facepunch.Steamworks.Workshop.Item item, GUIListBox listBox, string previewImagePath) { - while (pendingPreviewImageDownloads.Contains(item.PreviewImageUrl)) + while (true) { + lock (pendingPreviewImageDownloads) + { + if (!pendingPreviewImageDownloads.Contains(item.PreviewImageUrl)) { break; } + } + yield return CoroutineStatus.Running; } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index c33ff80a0..8213e7591 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -522,11 +522,6 @@ namespace Barotrauma OnSelected = (GUITickBox obj) => { Gap.ShowGaps = obj.Selected; return true; }, }; - tickBoxHolder.Children.ForEach(c => - { - if (c is GUITickBox tb) { tb.RectTransform.MinSize = new Point(0, 32); } - }); - GUITextBlock.AutoScaleAndNormalize(tickBoxHolder.Children.Where(c => c is GUITickBox).Select(c => ((GUITickBox)c).TextBlock)); //spacing diff --git a/Barotrauma/BarotraumaClient/Source/Sprite/DeformAnimations/SpriteDeformation.cs b/Barotrauma/BarotraumaClient/Source/Sprite/DeformAnimations/SpriteDeformation.cs index da7e27603..4df76b354 100644 --- a/Barotrauma/BarotraumaClient/Source/Sprite/DeformAnimations/SpriteDeformation.cs +++ b/Barotrauma/BarotraumaClient/Source/Sprite/DeformAnimations/SpriteDeformation.cs @@ -14,7 +14,7 @@ namespace Barotrauma.SpriteDeformations /// A positive value means that this deformation is or could be used for multiple sprites. /// This behaviour is not automatic, and has to be implemented for any particular case separately (currently only used in Limbs). /// - [Serialize(-1, true), Editable] + [Serialize(-1, true), Editable(minValue: -1, maxValue: 100)] public int Sync { get; diff --git a/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs index 75f482e3d..00806fe99 100644 --- a/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs @@ -64,7 +64,7 @@ namespace Barotrauma { foreach (RoundSound sound in sounds) { - if (sound.Sound == null) + if (sound?.Sound == null) { string errorMsg = $"Error in StatusEffect.ApplyProjSpecific1 (sound \"{sound.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace; GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull1" + Environment.StackTrace, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); @@ -90,7 +90,7 @@ namespace Barotrauma selectedSoundIndex = Rand.Int(sounds.Count); } var selectedSound = sounds[selectedSoundIndex]; - if (selectedSound.Sound == null) + if (selectedSound?.Sound == null) { string errorMsg = $"Error in StatusEffect.ApplyProjSpecific2 (sound \"{selectedSound.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace; GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull2" + Environment.StackTrace, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index bd05b4084..8a14fa83b 100644 --- a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.9.5.1")] -[assembly: AssemblyFileVersion("0.9.5.1")] +[assembly: AssemblyVersion("0.9.6.0")] +[assembly: AssemblyFileVersion("0.9.6.0")] diff --git a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs index a01d48706..ed5715abc 100644 --- a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs @@ -268,8 +268,8 @@ namespace Barotrauma break; case NetEntityEvent.Type.Control: msg.WriteRangedInteger(1, 0, 3); - Client owner = ((Client)extraData[1]); - msg.Write(owner == null ? (byte)0 : owner.ID); + Client owner = (Client)extraData[1]; + msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0); break; case NetEntityEvent.Type.Status: msg.WriteRangedInteger(2, 0, 3); @@ -445,14 +445,14 @@ namespace Barotrauma } } - public void WriteSpawnData(IWriteMessage msg) + public void WriteSpawnData(IWriteMessage msg, UInt16 entityId) { if (GameMain.Server == null) return; int msgLength = msg.LengthBytes; msg.Write(Info == null); - msg.Write(ID); + msg.Write(entityId); msg.Write(SpeciesName); msg.Write(seed); diff --git a/Barotrauma/BarotraumaServer/Source/GameMain.cs b/Barotrauma/BarotraumaServer/Source/GameMain.cs index 7f337c4ab..10d5b46cf 100644 --- a/Barotrauma/BarotraumaServer/Source/GameMain.cs +++ b/Barotrauma/BarotraumaServer/Source/GameMain.cs @@ -73,7 +73,7 @@ namespace Barotrauma { Instance = this; - CommandLineArgs = args; + CommandLineArgs = ToolBox.MergeArguments(args); World = new World(new Vector2(0, -9.82f)); FarseerPhysics.Settings.AllowSleep = true; @@ -189,6 +189,13 @@ namespace Barotrauma ownerKey = 0; } +#if DEBUG + foreach (string s in CommandLineArgs) + { + Console.WriteLine(s); + } +#endif + for (int i = 0; i < CommandLineArgs.Length; i++) { switch (CommandLineArgs[i].Trim()) @@ -213,6 +220,9 @@ namespace Barotrauma password = CommandLineArgs[i + 1]; i++; break; + case "-nopassword": + password = ""; + break; case "-upnp": case "-enableupnp": bool.TryParse(CommandLineArgs[i + 1], out enableUpnp); @@ -303,6 +313,7 @@ namespace Barotrauma Server.Update((float)Timing.Step); if (Server == null) { break; } SteamManager.Update((float)Timing.Step); + TaskPool.Update(); CoroutineManager.Update((float)Timing.Step, (float)Timing.Step); Timing.Accumulator -= Timing.Step; diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs index bfa85f702..ce3e99c35 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs @@ -7,7 +7,7 @@ namespace Barotrauma.Items.Components { partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen) { - if (isStuck || isOpen == open) + if (IsStuck || isOpen == open) { return; } diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Controller.cs index 21c3e65cf..7bdbac370 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Controller.cs @@ -7,6 +7,7 @@ namespace Barotrauma.Items.Components public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) { msg.Write(state); + msg.Write(user == null ? (ushort)0 : user.ID); } } } diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs index 443638156..43b056872 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs @@ -38,7 +38,7 @@ namespace Barotrauma.Items.Components msg.Write(deteriorationTimer); msg.Write(deteriorateAlwaysResetTimer); msg.Write(DeteriorateAlways); - msg.Write(CurrentFixer == c.Character); + msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID); msg.WriteRangedInteger((int)currentFixerAction, 0, 2); } } diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs index cd0f7b77c..dc731f6da 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs @@ -32,7 +32,6 @@ namespace Barotrauma.Items.Components } } - List clientSideDisconnectedWires = new List(); ushort disconnectedWireCount = msg.ReadUInt16(); for (int i = 0; i < disconnectedWireCount; i++) @@ -179,6 +178,7 @@ namespace Barotrauma.Items.Components public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) { + msg.Write(user == null ? (ushort)0 : user.ID); ClientWrite(msg, extraData); } } diff --git a/Barotrauma/BarotraumaServer/Source/Items/Item.cs b/Barotrauma/BarotraumaServer/Source/Items/Item.cs index 747efe98f..280e7c1b4 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Item.cs @@ -100,16 +100,26 @@ namespace Barotrauma { ActionType actionType = (ActionType)extraData[1]; ItemComponent targetComponent = extraData.Length > 2 ? (ItemComponent)extraData[2] : null; - ushort targetID = extraData.Length > 3 ? (ushort)extraData[3] : (ushort)0; + ushort characterID = extraData.Length > 3 ? (ushort)extraData[3] : (ushort)0; Limb targetLimb = extraData.Length > 4 ? (Limb)extraData[4] : null; + ushort useTargetID = extraData.Length > 5 ? (ushort)extraData[5] : (ushort)0; + Vector2? worldPosition = null; + if (extraData.Length > 6) { worldPosition = (Vector2)extraData[6]; } - Character targetCharacter = FindEntityByID(targetID) as Character; + Character targetCharacter = FindEntityByID(characterID) as Character; byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255; msg.WriteRangedInteger((int)actionType, 0, Enum.GetValues(typeof(ActionType)).Length - 1); msg.Write((byte)(targetComponent == null ? 255 : components.IndexOf(targetComponent))); - msg.Write(targetID); + msg.Write(characterID); msg.Write(targetLimbIndex); + msg.Write(useTargetID); + msg.Write(worldPosition.HasValue); + if (worldPosition.HasValue) + { + msg.Write(worldPosition.Value.X); + msg.Write(worldPosition.Value.Y); + } } break; case NetEntityEvent.Type.ChangeProperty: @@ -197,7 +207,7 @@ namespace Barotrauma } } - public void WriteSpawnData(IWriteMessage msg) + public void WriteSpawnData(IWriteMessage msg, UInt16 entityID) { if (GameMain.Server == null) return; @@ -209,7 +219,7 @@ namespace Barotrauma msg.Write(Description); } - msg.Write(ID); + msg.Write(entityID); if (ParentInventory == null || ParentInventory.Owner == null) { diff --git a/Barotrauma/BarotraumaServer/Source/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaServer/Source/Networking/EntitySpawner.cs index f899b254d..6c417c34f 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/EntitySpawner.cs @@ -25,24 +25,23 @@ namespace Barotrauma SpawnOrRemove entities = (SpawnOrRemove)extraData[0]; message.Write(entities.Remove); - if (entities.Remove) { - message.Write(entities.Entity.ID); + message.Write(entities.OriginalID); } else { if (entities.Entity is Item) { message.Write((byte)SpawnableType.Item); - DebugConsole.Log("Writing item spawn data " + entities.Entity.ToString() + " (ID: " + entities.Entity.ID + ")"); - ((Item)entities.Entity).WriteSpawnData(message); + DebugConsole.Log("Writing item spawn data " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")"); + ((Item)entities.Entity).WriteSpawnData(message, entities.OriginalID); } else if (entities.Entity is Character) { message.Write((byte)SpawnableType.Character); - DebugConsole.Log("Writing character spawn data: " + entities.Entity.ToString() + " (ID: " + entities.Entity.ID + ")"); - ((Character)entities.Entity).WriteSpawnData(message); + DebugConsole.Log("Writing character spawn data: " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")"); + ((Character)entities.Entity).WriteSpawnData(message, entities.OriginalID); } } } diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs index fd7ed2d95..f4ae2d883 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs @@ -134,10 +134,7 @@ namespace Barotrauma.Networking serverSettings = new ServerSettings(this, name, port, queryPort, maxPlayers, isPublic, attemptUPnP); KarmaManager.SelectPreset(serverSettings.KarmaPreset); - if (!string.IsNullOrEmpty(password)) - { - serverSettings.SetPassword(password); - } + serverSettings.SetPassword(password); ownerKey = ownKey; @@ -874,7 +871,9 @@ namespace Barotrauma.Networking if (Level.Loaded != null && levelEqualityCheckVal != Level.Loaded.EqualityCheckVal) { - errorStr += " Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ")."; + errorStr += " Level equality check failed. The level generated at your end doesn't match the level generated by the server(seed: " + Level.Loaded.Seed + + ", sub: " + Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash.ShortHash + ")" + + ", mirrored: " + Level.Loaded.Mirrored + ")."; } Log(c.Name + " has reported an error: " + errorStr, ServerLog.MessageType.Error); diff --git a/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs index a1e7b414b..57f56610d 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs @@ -306,6 +306,7 @@ namespace Barotrauma.Networking } ServerName = doc.Root.GetAttributeString("name", ""); + if (ServerName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); } ServerMessageText = doc.Root.GetAttributeString("ServerMessage", ""); GameMain.NetLobbyScreen.SelectedModeIdentifier = GameModeIdentifier; diff --git a/Barotrauma/BarotraumaShared/SharedCode.projitems b/Barotrauma/BarotraumaShared/SharedCode.projitems index b7ffe114d..df23cb64e 100644 --- a/Barotrauma/BarotraumaShared/SharedCode.projitems +++ b/Barotrauma/BarotraumaShared/SharedCode.projitems @@ -283,6 +283,7 @@ + diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs index 553150231..f05718dab 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs @@ -21,7 +21,7 @@ namespace Barotrauma /// /// How long does it take for the ai target to fade out if not kept alive. /// - public float FadeOutTime { get; private set; } + public float FadeOutTime { get; private set; } = 1; public bool Static { get; private set; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index e89761972..e493866ff 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -611,6 +611,7 @@ namespace Barotrauma } bool canAttack = true; + bool pursue = false; if (IsCoolDownRunning) { switch (AttackingLimb.attack.AfterAttack) @@ -623,6 +624,7 @@ namespace Barotrauma if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue) { canAttack = false; + pursue = true; } else { @@ -661,6 +663,7 @@ namespace Barotrauma if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue) { canAttack = false; + pursue = true; } else { @@ -853,32 +856,35 @@ namespace Barotrauma if (pathSteering.CurrentPath != null) { // Attack doors - if (canAttackSub && pathSteering.CurrentPath.CurrentNode?.ConnectedDoor != null && SelectedAiTarget != pathSteering.CurrentPath.CurrentNode.ConnectedDoor.Item.AiTarget) + if (canAttackSub) { - SelectTarget(pathSteering.CurrentPath.CurrentNode.ConnectedDoor.Item.AiTarget); - return; + // If the target is in the same hull, there shouldn't be any doors blocking the path + if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull) + { + var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor; + if (door != null && !door.IsOpen && door.Item.Condition > 0) + { + if (SelectedAiTarget != door.Item.AiTarget) + { + SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority); + return; + } + } + } } - else if (canAttackSub && pathSteering.CurrentPath.NextNode?.ConnectedDoor != null && SelectedAiTarget != pathSteering.CurrentPath.NextNode.ConnectedDoor.Item.AiTarget) + // Steer towards the target if in the same room and swimming + if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull)) { - SelectTarget(pathSteering.CurrentPath.NextNode.ConnectedDoor.Item.AiTarget); - return; + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition)); } else { - // Steer towards the target if in the same room and swimming - if (Character.AnimController.InWater && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull)) + SteeringManager.SteeringSeek(steerPos, 2); + // Switch to Idle when cannot reach the target and if cannot damage the walls + if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) { - SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition)); - } - else - { - SteeringManager.SteeringSeek(steerPos, 2); - // Switch to Idle when cannot reach the target and if cannot damage the walls - if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) - { - State = AIState.Idle; - return; - } + State = AIState.Idle; + return; } } } @@ -1148,8 +1154,8 @@ namespace Barotrauma { selectedTargetMemory.Priority = 0; } - return true; } + return true; } return false; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs index feb1411d7..364ffbc05 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs @@ -525,6 +525,7 @@ namespace Barotrauma protected void ReportProblems() { Order newOrder = null; + Hull targetHull = null; if (Character.CurrentHull != null) { foreach (var hull in VisibleHulls) @@ -534,21 +535,21 @@ namespace Barotrauma if (c.CurrentHull != hull || !c.Enabled) { continue; } if (AIObjectiveFightIntruders.IsValidTarget(c, Character)) { - AddTargets(Character, c); - if (newOrder == null) + if (AddTargets(Character, c) && newOrder == null) { var orderPrefab = Order.GetPrefab("reportintruders"); - newOrder = new Order(orderPrefab, c.CurrentHull, null, orderGiver: Character); + newOrder = new Order(orderPrefab, hull, null, orderGiver: Character); + targetHull = hull; } } } if (AIObjectiveExtinguishFires.IsValidTarget(hull, Character)) { - AddTargets(Character, hull); - if (newOrder == null) + if (AddTargets(Character, hull) && newOrder == null) { var orderPrefab = Order.GetPrefab("reportfire"); newOrder = new Order(orderPrefab, hull, null, orderGiver: Character); + targetHull = hull; } } foreach (Character c in Character.CharacterList) @@ -556,13 +557,11 @@ namespace Barotrauma if (c.CurrentHull != hull) { continue; } if (AIObjectiveRescueAll.IsValidTarget(c, Character)) { - if (AddTargets(c, Character)) + if (AddTargets(c, Character) && newOrder == null && !ObjectiveManager.HasActiveObjective()) { - if (newOrder == null) - { - var orderPrefab = Order.GetPrefab("requestfirstaid"); - newOrder = new Order(orderPrefab, c.CurrentHull, null, orderGiver: Character); - } + var orderPrefab = Order.GetPrefab("requestfirstaid"); + newOrder = new Order(orderPrefab, hull, null, orderGiver: Character); + targetHull = hull; } } } @@ -570,11 +569,11 @@ namespace Barotrauma { if (AIObjectiveFixLeaks.IsValidTarget(gap, Character)) { - AddTargets(Character, gap); - if (newOrder == null && !gap.IsRoomToRoom) + if (AddTargets(Character, gap) && newOrder == null && !gap.IsRoomToRoom) { var orderPrefab = Order.GetPrefab("reportbreach"); newOrder = new Order(orderPrefab, hull, null, orderGiver: Character); + targetHull = hull; } } } @@ -584,11 +583,11 @@ namespace Barotrauma if (AIObjectiveRepairItems.IsValidTarget(item, Character)) { if (item.Repairables.All(r => item.ConditionPercentage > r.ShowRepairUIThreshold)) { continue; } - AddTargets(Character, item); - if (newOrder == null) + if (AddTargets(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective()) { var orderPrefab = Order.GetPrefab("reportbrokendevices"); - newOrder = new Order(orderPrefab, item.CurrentHull, item.Repairables?.FirstOrDefault(), orderGiver: Character); + newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character); + targetHull = hull; } } } @@ -598,9 +597,9 @@ namespace Barotrauma { if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime)) { - Character.Speak(newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order); + Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order); #if SERVER - GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder, "", Character.CurrentHull, null, Character)); + GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder, "", targetHull, null, Character)); #endif } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs index e7aead8d7..59813cd63 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs @@ -191,7 +191,8 @@ namespace Barotrauma { diff.Y = 0.0f; } - if (diff.LengthSquared() < 0.001f) { return -host.Steering; } + //if (diff.LengthSquared() < 0.001f) { return -host.Steering; } + if (diff == Vector2.Zero) { return Vector2.Zero; } return Vector2.Normalize(diff) * weight; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs index 312e53262..765872d6b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs @@ -71,7 +71,7 @@ namespace Barotrauma foreach (FireSource fs in targetHull.FireSources) { bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range)); - bool move = !inRange; + bool move = !inRange || !HumanAIController.VisibleHulls.Contains(fs.Hull); if (inRange || useExtinquisherTimer > 0.0f) { useExtinquisherTimer += deltaTime; @@ -79,7 +79,6 @@ namespace Barotrauma { useExtinquisherTimer = 0.0f; } - character.AIController.SteeringManager.Reset(); character.CursorPosition = fs.Position; if (extinguisher.Item.RequireAimToUse) { @@ -106,20 +105,19 @@ namespace Barotrauma { sightLimb = character.AnimController.GetLimb(LimbType.LeftHand); } - if (!character.CanSeeTarget(fs, sightLimb)) + if (character.CanSeeTarget(fs, sightLimb)) { - move = true; - } - else - { - move = false; character.SetInput(extinguisher.Item.IsShootable ? InputType.Shoot : InputType.Use, false, true); extinguisher.Use(deltaTime, character); if (!targetHull.FireSources.Contains(fs)) - { - character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.Name, true), null, 0, "putoutfire", 10.0f); + { + character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.RoomName, true), null, 0, "putoutfire", 10.0f); } } + else + { + move = true; + } } if (move) { @@ -128,6 +126,10 @@ namespace Barotrauma onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref gotoObjective)); } + else + { + character.AIController.SteeringManager.Reset(); + } break; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs index d6c965eed..a21460a6d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs @@ -11,7 +11,7 @@ namespace Barotrauma { public override string DebugTag => "fight intruders"; protected override float IgnoreListClearInterval => 30; - public virtual bool IgnoreUnsafeHulls => true; + public override bool IgnoreUnsafeHulls => true; public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs index f2ccf61ef..e14e9c5c5 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -200,7 +200,7 @@ namespace Barotrauma { SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10); } - if (!insideSteering) + if (!insideSteering && character.CurrentHull == null) { SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 1); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs index 012d51175..42bd4944b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs @@ -293,7 +293,7 @@ namespace Barotrauma newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true, - // Don't override auto pilot unless it's an order by a player + // Don't override unless it's an order by a player Override = orderGiver == Character.Controlled || orderGiver.IsRemotePlayer }; break; @@ -302,7 +302,7 @@ namespace Barotrauma newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true, - // Don't override auto control unless it's an order by a player + // Don't override unless it's an order by a player Override = orderGiver == Character.Controlled || orderGiver.IsRemotePlayer }; break; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs index f73da37fa..477dfe246 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -24,6 +24,8 @@ namespace Barotrauma public Entity OperateTarget => operateTarget; public ItemComponent Component => component; + public ItemComponent GetTarget() => useController ? controller : component; + public Func completionCondition; public override float GetPriority() @@ -35,6 +37,7 @@ namespace Barotrauma } if (component.Item.CurrentHull == null) { return 0; } if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; } + if (IsOperatedByAnother(GetTarget())) { return 0; } if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; } float devotion = MathHelper.Min(10, Priority); float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier; @@ -58,6 +61,45 @@ namespace Barotrauma } } + private bool IsOperatedByAnother(ItemComponent target) + { + foreach (var c in Character.CharacterList) + { + if (c == character) { continue; } + if (!HumanAIController.IsFriendly(c)) { continue; } + if (c.SelectedConstruction != target.Item) { continue; } + // If the other character is player, don't try to operate + if (c.IsRemotePlayer || Character.Controlled == c) { return true; } + if (c.AIController is HumanAIController humanAi) + { + // If the other character is ordered to operate the item, let him do it + if (humanAi.ObjectiveManager.IsCurrentOrder()) + { + return true; + } + else + { + if (target is Steering) + { + // Steering is hard-coded -> cannot use the required skills collection defined in the xml + return character.GetSkillLevel("helm") <= c.GetSkillLevel("helm"); + } + else + { + return target.DegreeOfSuccess(character) <= target.DegreeOfSuccess(c); + } + } + } + else + { + // Shouldn't go here, unless we allow non-humans to operate items + return false; + } + + } + return false; + } + protected override void Act(float deltaTime) { if (character.LockHands) @@ -65,24 +107,24 @@ namespace Barotrauma Abandon = true; return; } - ItemComponent target = useController ? controller : component; + ItemComponent target = GetTarget(); if (useController && controller == null) { character.Speak(TextManager.GetWithVariable("DialogCantFindController", "[item]", component.Item.Name, true), null, 2.0f, "cantfindcontroller", 30.0f); Abandon = true; return; } + // Don't allow to operate an item that someone with a better skills already operates, unless this is an order + if (objectiveManager.CurrentOrder != this && IsOperatedByAnother(target)) + { + // Don't abandon + return; + } if (target.CanBeSelected) { if (character.CanInteractWith(target.Item, out _, checkLinked: false)) { HumanAIController.FaceTarget(target.Item); - // Don't allow to operate an item that someone already operates, unless this objective is an order - if (objectiveManager.CurrentOrder != this && Character.CharacterList.Any(c => c.SelectedConstruction == target.Item && c != character && HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) - { - // Don't abandon - return; - } if (character.SelectedConstruction != target.Item) { target.Item.TryInteract(character, false, true); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index e0c689cc3..577c9fc03 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -764,12 +764,17 @@ namespace Barotrauma .Where(p => p.AfflictionType == "huskinfection") .Select(p => p as AfflictionPrefabHusk) .FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.InvariantCultureIgnoreCase))); + string nonHuskedSpeciesName = string.Empty; if (matchingAffliction == null) { DebugConsole.ThrowError("Cannot find a husk infection that matches this species! Please add the speciesnames as 'targets' in the husk affliction prefab definition!"); - return; + // Crashes if we fail to create a ragdoll -> Let's just use some ragdoll so that the user sees the error msg. + nonHuskedSpeciesName = IsHumanoid ? HumanSpeciesName : "crawler"; + } + else + { + nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction); } - string nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction); ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams(nonHuskedSpeciesName) : RagdollParams.GetDefaultRagdollParams(nonHuskedSpeciesName) as RagdollParams; if (info == null) { @@ -1912,7 +1917,7 @@ namespace Barotrauma { if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen) { - focusedCharacter = FindCharacterAtPosition(mouseSimPos); + focusedCharacter = CanInteract ? FindCharacterAtPosition(mouseSimPos) : null; focusedItem = CanInteract ? FindItemAtPosition(mouseSimPos, GameMain.Config.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f)) : null; findFocusedTimer = 0.05f; @@ -1972,11 +1977,11 @@ namespace Barotrauma { DeselectCharacter(); } - else if (focusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged) + else if (focusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged && CanInteract) { SelectCharacter(focusedCharacter); } - else if (focusedCharacter != null && IsKeyHit(InputType.Health) && focusedCharacter.CharacterHealth.UseHealthWindow && CanInteractWith(focusedCharacter, 160f, false)) + else if (focusedCharacter != null && IsKeyHit(InputType.Health) && focusedCharacter.CharacterHealth.UseHealthWindow && CanInteract && CanInteractWith(focusedCharacter, 160f, false)) { if (focusedCharacter == SelectedCharacter) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionHusk.cs index f62c1181b..05a292341 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionHusk.cs @@ -289,7 +289,7 @@ namespace Barotrauma public static string GetNonHuskedSpeciesName(string huskedSpeciesName, AfflictionPrefabHusk prefab) { string nonTag = prefab.HuskedSpeciesName.Remove(AfflictionPrefabHusk.Tag); - return huskedSpeciesName.Remove(nonTag); + return huskedSpeciesName.ToLowerInvariant().Remove(nonTag); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs index 78be69b4d..21c7d7e72 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -41,7 +41,7 @@ namespace Barotrauma { public AfflictionPrefabHusk(XElement element, Type type = null) : base(element, type) { - HuskedSpeciesName = element.GetAttributeString("huskedspeciesname", null); + HuskedSpeciesName = element.GetAttributeString("huskedspeciesname", null).ToLowerInvariant(); if (HuskedSpeciesName == null) { DebugConsole.NewMessage($"No 'huskedspeciesname' defined for the husk affliction ({Identifier}) in {element.ToString()}", Color.Orange); diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs index 2ff3d82a5..77fe72dd3 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs @@ -85,7 +85,11 @@ namespace Barotrauma public override void Update(float deltaTime) { - if (IsClient) { return; } + if (IsClient) + { + if (item.ParentInventory != null) { item.body.FarseerBody.IsKinematic = false; } + return; + } switch (State) { case 0: diff --git a/Barotrauma/BarotraumaShared/Source/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs index e781bcc15..2337eba4f 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSettings.cs @@ -296,10 +296,11 @@ namespace Barotrauma { get { -#if DEBUG + return false; +/*#if DEBUG return false; #endif - return sendUserStatistics; + return sendUserStatistics;*/ } set { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index d69d69a2b..5df6f7ee2 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -25,7 +25,18 @@ namespace Barotrauma.Items.Components private readonly bool autoOrientGap; private bool isStuck; - public bool IsStuck => isStuck; + public bool IsStuck + { + get { return isStuck; } + private set + { + if (isStuck == value) { return; } + isStuck = value; +#if SERVER + item.CreateServerEvent(this); +#endif + } + } private float resetPredictionTimer; @@ -65,12 +76,12 @@ namespace Barotrauma.Items.Components public float Stuck { get { return stuck; } - set + set { if (isOpen || isBroken || !CanBeWelded) return; stuck = MathHelper.Clamp(value, 0.0f, 100.0f); - if (stuck <= 0.0f) isStuck = false; - if (stuck >= 100.0f) isStuck = true; + if (stuck <= 0.0f) { IsStuck = false; } + if (stuck >= 100.0f) { IsStuck = true; } } } @@ -296,7 +307,7 @@ namespace Barotrauma.Items.Components } bool isClosing = false; - if (!isStuck) + if (!IsStuck) { if (PredictedState == null) { @@ -541,7 +552,7 @@ namespace Barotrauma.Items.Components public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f) { - if (isStuck) return; + if (IsStuck) return; bool wasOpen = PredictedState == null ? isOpen : PredictedState.Value; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ElectricalDischarger.cs index 76aaf4f52..e08385827 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ElectricalDischarger.cs @@ -9,7 +9,7 @@ namespace Barotrauma.Items.Components { partial class ElectricalDischarger : Powered { - private static List list = new List(); + private static readonly List list = new List(); public static IEnumerable List { get { return list; } @@ -48,14 +48,14 @@ namespace Barotrauma.Items.Components } } - [Serialize(100.0f, true, description: "How far the discharge can travel from the item."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)] + [Serialize(500.0f, true, description: "How far the discharge can travel from the item."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)] public float Range { get; set; } - [Serialize(10.0f, true, description: "How much further can the discharge be carried when moving across walls."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)] + [Serialize(25.0f, true, description: "How much further can the discharge be carried when moving across walls."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)] public float RangeMultiplierInWalls { get; @@ -127,20 +127,42 @@ namespace Barotrauma.Items.Components #if CLIENT frameOffset = Rand.Int(electricitySprite.FrameCount); #endif - if (timer > 0.0f) - { - if (charging) - { - if (Voltage > MinVoltage) - { - Discharge(); - } - } - timer -= deltaTime; - } - else + if (timer <= 0.0f) { IsActive = false; + return; + } + + timer -= deltaTime; + if (charging) + { + if (GetAvailableBatteryPower() >= powerConsumption) + { + var batteries = item.GetConnectedComponents(); + float neededPower = powerConsumption; + while (neededPower > 0.0001f && batteries.Count > 0) + { + batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f); + float takePower = neededPower / batteries.Count; + takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut))); + foreach (PowerContainer battery in batteries) + { + neededPower -= takePower; + battery.Charge -= takePower / 3600.0f; + #if SERVER + if (GameMain.Server != null) + { + battery.Item.CreateServerEvent(battery); + } + #endif + } + } + Discharge(); + } + else if (Voltage > MinVoltage) + { + Discharge(); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs index 2610644be..17c20929c 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs @@ -141,7 +141,7 @@ namespace Barotrauma.Items.Components //TODO: refactor the hitting logic (get rid of the magic numbers, make it possible to use different kinds of animations for different items) if (!hitting) { - bool aim = picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent() != null); + bool aim = picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent() != null); if (aim) { hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 5f, MathHelper.PiOver4)); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Propulsion.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Propulsion.cs index 82e8c5b4c..023d590cf 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Propulsion.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Propulsion.cs @@ -96,11 +96,14 @@ namespace Barotrauma.Items.Components { useState -= deltaTime; - if (useState <= 0.0f) IsActive = false; - - if (item.AiTarget != null) + if (useState <= 0.0f) { - item.AiTarget.SoundRange = IsActive ? item.AiTarget.MaxSoundRange : item.AiTarget.MinSoundRange; + IsActive = false; + } + + if (item.AiTarget != null && IsActive) + { + item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange; } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs index a7adf639f..1c5d37d66 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs @@ -399,7 +399,7 @@ namespace Barotrauma.Items.Components case "activate": case "use": case "trigger_in": - item.Use(1.0f); + item.Use(1.0f, sender); break; case "toggle": if (signal != "0") @@ -669,7 +669,7 @@ namespace Barotrauma.Items.Components } } - public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Character user = null) + public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null) { if (statusEffectLists == null) return; @@ -680,20 +680,24 @@ namespace Barotrauma.Items.Components { if (broken && effect.type != ActionType.OnBroken) { continue; } if (user != null) { effect.SetUser(user); } - item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, false, false); + item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, false, false, worldPosition); } } public virtual void Load(XElement componentElement, bool usePrefabValues) { - if (componentElement == null || usePrefabValues) { return; } - foreach (XAttribute attribute in componentElement.Attributes()) - { - if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue; - property.TrySetValue(this, attribute.Value); + if (componentElement != null && !usePrefabValues) + { + foreach (XAttribute attribute in componentElement.Attributes()) + { + if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue; + property.TrySetValue(this, attribute.Value); + } + ParseMsg(); + OverrideRequiredItems(componentElement); } - ParseMsg(); - OverrideRequiredItems(componentElement); + + if (item.Submarine != null) { SerializableProperty.UpgradeGameVersion(this, originalElement, item.Submarine.GameVersion); } } /// diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs index 319ea9f53..5c7621173 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs @@ -123,9 +123,9 @@ namespace Barotrauma.Items.Components if (user.AnimController.InWater) { - if (diff.Length() > 30.0f) + if (diff.LengthSquared() > 30.0f * 30.0f) { - user.AnimController.TargetMovement = Vector2.Clamp(diff*0.01f, -Vector2.One, Vector2.One); + user.AnimController.TargetMovement = Vector2.Clamp(diff * 0.01f, -Vector2.One, Vector2.One); user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left; } else @@ -136,11 +136,29 @@ namespace Barotrauma.Items.Components else { diff.Y = 0.0f; - if (diff != Vector2.Zero && diff.LengthSquared() > 10.0f * 10.0f) + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled) { - user.AnimController.TargetMovement = Vector2.Normalize(diff); - user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left; - return; + if (Math.Abs(diff.X) > 20.0f) + { + //wait for the character to walk to the correct position + return; + } + else if (Math.Abs(diff.X) > 0.1f) + { + //aim to keep the collider at the correct position once close enough + user.AnimController.Collider.LinearVelocity = new Vector2( + diff.X * 0.1f, + user.AnimController.Collider.LinearVelocity.Y); + } + } + else + { + if (Math.Abs(diff.X) > 10.0f) + { + user.AnimController.TargetMovement = Vector2.Normalize(diff); + user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left; + return; + } } user.AnimController.TargetMovement = Vector2.Zero; } @@ -293,7 +311,7 @@ namespace Barotrauma.Items.Components private void CancelUsing(Character character) { - if (character == null || character.Removed) return; + if (character == null || character.Removed) { return; } foreach (LimbPos lb in limbPositions) { @@ -304,18 +322,21 @@ namespace Barotrauma.Items.Components limb.PullJointEnabled = false; } - if (character.SelectedConstruction == this.item) character.SelectedConstruction = null; + if (character.SelectedConstruction == this.item) { character.SelectedConstruction = null; } character.AnimController.Anim = AnimController.Animation.None; if (character == Character.Controlled) { HideHUDs(false); } +#if SERVER + item.CreateServerEvent(this); +#endif } public override bool Select(Character activator) { - if (activator == null || activator.Removed) return false; + if (activator == null || activator.Removed) { return false; } //someone already using the item if (user != null && !user.Removed) @@ -330,10 +351,12 @@ namespace Barotrauma.Items.Components } else { - user = activator; + user = activator; IsActive = true; } - +#if SERVER + item.CreateServerEvent(this); +#endif item.SendSignal(0, "1", "signal_out", user); return true; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs index 862b767c1..045e5ffbd 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs @@ -86,7 +86,7 @@ namespace Barotrauma.Items.Components if (item.CurrentHull == null) { return; } - float powerFactor = currPowerConsumption <= 0.0f ? 1.0f : Voltage; + float powerFactor = Math.Min(currPowerConsumption <= 0.0f ? 1.0f : Voltage, 1.0f); currFlow = flowPercentage / 100.0f * maxFlow * powerFactor; //less effective when in a bad condition diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs index 351ff33c9..3971643df 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs @@ -640,9 +640,15 @@ namespace Barotrauma.Items.Components } } - if (lastUser != character && lastUser != null && lastUser.SelectedConstruction == item) + if (objective.Override) { - character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f); + if (lastUser != null && lastUser != character && lastUser != lastAIUser) + { + if (lastUser.SelectedConstruction == item) + { + character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f); + } + } } LastUser = lastAIUser = character; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs index 098478821..2f0f9dc91 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs @@ -479,9 +479,12 @@ namespace Barotrauma.Items.Components public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { - if (user != character && user != null && user.SelectedConstruction == item) + if (objective.Override) { - character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f); + if (user != character && user != null && user.SelectedConstruction == item) + { + character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f); + } } user = character; if (!AutoPilot) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs index 38487261f..f2d9706b1 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs @@ -175,9 +175,8 @@ namespace Barotrauma.Items.Components else { currPowerConsumption = MathHelper.Lerp(currPowerConsumption, rechargeSpeed, 0.05f); - Charge += currPowerConsumption * Voltage / 3600.0f; - } - + Charge += currPowerConsumption * Math.Min(Voltage, 1.0f) / 3600.0f; + } if (charge <= 0.0f) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs index 7d6e347ee..7de229b08 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs @@ -280,6 +280,8 @@ namespace Barotrauma.Items.Components { //we've already received this signal if (lastPowerProbeRecipients.Contains(this)) { return; } + if (item.Condition <= 0.0f) { return; } + lastPowerProbeRecipients.Add(this); if (power < 0.0f) @@ -306,15 +308,15 @@ namespace Barotrauma.Items.Components foreach (ItemComponent ic in recipient.Item.Components) { - //powertransfer components don't need to receive the signal in the pass-through signal connections + //other junction boxes don't need to receive the signal in the pass-through signal connections //because we relay it straight to the connected items without going through the whole chain of junction boxes - if (ic is PowerTransfer && connection.Name.Contains("signal")) { continue; } + if (ic is PowerTransfer && !(ic is RelayComponent) && connection.Name.Contains("signal")) { continue; } ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, 0.0f, signalStrength); } foreach (StatusEffect effect in recipient.Effects) { - recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, null, null, false, false); + recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs index 37c452648..1049831b0 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs @@ -279,13 +279,30 @@ namespace Barotrauma.Items.Components var pc = powerSource.Item.GetComponent(); if (pc != null) { - float voltage = -pc.CurrPowerOutput / Math.Max(powered.CurrPowerConsumption, 1.0f); + float voltage = pc.CurrPowerOutput / Math.Max(powered.CurrPowerConsumption, 1.0f); powered.voltage += voltage; } } } } - + + /// + /// Returns the amount of power that can be supplied by batteries directly connected to the item + /// + protected float GetAvailableBatteryPower() + { + var batteries = item.GetConnectedComponents(); + + float availablePower = 0.0f; + foreach (PowerContainer battery in batteries) + { + float batteryPower = Math.Min(battery.Charge * 3600.0f, battery.MaxOutPut); + availablePower += batteryPower; + } + + return availablePower; + } + protected override void RemoveComponentSpecific() { poweredList.Remove(this); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs index c9fd76605..38148622f 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs @@ -458,6 +458,11 @@ namespace Barotrauma.Items.Components if (character != null) { character.LastDamageSource = item; } +#if CLIENT + PlaySound(ActionType.OnUse, item.WorldPosition, user: user); + PlaySound(ActionType.OnImpact, item.WorldPosition, user: user); +#endif + if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) { if (target.Body.UserData is Limb targetLimb) @@ -489,14 +494,26 @@ namespace Barotrauma.Items.Components } } } - } #if SERVER - if (GameMain.NetworkMember.IsServer) - { - GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse }); - GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact }); - } + if (GameMain.NetworkMember.IsServer) + { + GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, this, targetLimb.character.ID, targetLimb, (ushort)0, item.WorldPosition }); + GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact, this, targetLimb.character.ID, targetLimb, (ushort)0, item.WorldPosition }); + } #endif + } + else + { + ApplyStatusEffects(ActionType.OnUse, 1.0f, useTarget: target.Body.UserData as Entity, user: user); + ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: user); +#if SERVER + if (GameMain.NetworkMember.IsServer) + { + GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, this, (ushort)0, null, (target.Body.UserData as Entity)?.ID ?? 0, item.WorldPosition }); + GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact, this, (ushort)0, null, (target.Body.UserData as Entity)?.ID ?? 0, item.WorldPosition }); + } +#endif + } } item.body.FarseerBody.OnCollision -= OnProjectileCollision; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs index bc3b1c9b9..115bfd3be 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs @@ -130,6 +130,12 @@ namespace Barotrauma.Items.Components } else { +#if SERVER + if (CurrentFixer != character || currentFixerAction != action) + { + item.CreateServerEvent(this); + } +#endif CurrentFixer = character; CurrentFixerAction = action; return true; @@ -140,12 +146,15 @@ namespace Barotrauma.Items.Components { if (CurrentFixer == character) { +#if SERVER + if (CurrentFixer != character || currentFixerAction != FixActions.None) + { + item.CreateServerEvent(this); + } +#endif CurrentFixer.AnimController.Anim = AnimController.Animation.None; CurrentFixer = null; currentFixerAction = FixActions.None; -#if SERVER - item.CreateServerEvent(this); -#endif #if CLIENT repairSoundChannel?.FadeOutAndDispose(); repairSoundChannel = null; @@ -214,16 +223,16 @@ namespace Barotrauma.Items.Components return; } + UpdateFixAnimation(CurrentFixer); + + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + if (CurrentFixer != null && (CurrentFixer.SelectedConstruction != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead)) { StopRepairing(CurrentFixer); return; } - UpdateFixAnimation(CurrentFixer); - - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } - float successFactor = requiredSkills.Count == 0 ? 1.0f : DegreeOfSuccess(CurrentFixer, requiredSkills); //item must have been below the repair threshold for the player to get an achievement or XP for repairing it diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs index e029a9e9b..ada7e145b 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs @@ -258,7 +258,7 @@ namespace Barotrauma.Items.Components foreach (StatusEffect effect in recipient.Effects) { - recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false); + recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs index 686974715..dfc67d13a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs @@ -35,6 +35,11 @@ namespace Barotrauma.Items.Components set { /*do nothing*/ } } + public Character User + { + get { return user; } + } + public ConnectionPanel(Item item, XElement element) : base(item, element) { @@ -126,6 +131,9 @@ namespace Barotrauma.Items.Components if (user == null || user.SelectedConstruction != item) { +#if SERVER + if (user != null) { item.CreateServerEvent(this); } +#endif user = null; return; } @@ -135,6 +143,11 @@ namespace Barotrauma.Items.Components user.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f)); } + public override void UpdateBroken(float deltaTime, Camera cam) + { + Update(deltaTime, cam); + } + partial void UpdateProjSpecific(float deltaTime); public override bool Select(Character picker) @@ -147,13 +160,16 @@ namespace Barotrauma.Items.Components } user = picker; +#if SERVER + if (user != null) { item.CreateServerEvent(this); } +#endif IsActive = true; return true; } public override bool Use(float deltaTime, Character character = null) { - if (character == null || character != user) return false; + if (character == null || character != user) { return false; } var powered = item.GetComponent(); if (powered != null) @@ -257,6 +273,10 @@ namespace Barotrauma.Items.Components public void ClientWrite(IWriteMessage msg, object[] extraData = null) { +#if CLIENT + TriggerRewiringSound(); +#endif + foreach (Connection connection in Connections) { foreach (Wire wire in connection.Wires) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs index b98bd5348..b72dcacf1 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs @@ -172,7 +172,7 @@ namespace Barotrauma.Items.Components foreach (StatusEffect effect in ciElement.StatusEffects) { - item.ApplyStatusEffect(effect, ciElement.State ? ActionType.OnUse : ActionType.OnSecondaryUse, 1.0f, null, null, true, false); + item.ApplyStatusEffect(effect, ciElement.State ? ActionType.OnUse : ActionType.OnSecondaryUse, 1.0f, null, null, null, true, false); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs index 0f7a815d0..5187fe3ba 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs @@ -34,7 +34,7 @@ namespace Barotrauma.Items.Components { range = MathHelper.Clamp(value, 0.0f, 4096.0f); #if CLIENT - if (light != null) light.Range = range; + if (light != null) { light.Range = range; } #endif } } @@ -75,7 +75,7 @@ namespace Barotrauma.Items.Components get { return IsActive; } set { - if (IsActive == value) return; + if (IsActive == value) { return; } IsActive = value; #if SERVER @@ -135,11 +135,8 @@ namespace Barotrauma.Items.Components { if (base.IsActive == value) { return; } base.IsActive = value; -#if CLIENT - if (light == null) return; - light.Color = value ? lightColor : Color.Transparent; - if (!value) lightBrightness = 0.0f; -#endif + + SetLightSourceState(value, value ? lightBrightness : 0.0f); } } @@ -153,7 +150,8 @@ namespace Barotrauma.Items.Components Position = item.Position, CastShadows = castShadows, IsBackground = drawBehindSubs, - SpriteScale = Vector2.One * item.Scale + SpriteScale = Vector2.One * item.Scale, + Range = range }; #endif @@ -161,40 +159,34 @@ namespace Barotrauma.Items.Components item.AddTag("light"); } -#if CLIENT - public override void OnScaleChanged() - { - light.SpriteScale = Vector2.One * item.Scale; - light.Position = ParentBody != null ? ParentBody.Position : item.Position; - } -#endif - public override void OnItemLoaded() { base.OnItemLoaded(); itemLoaded = true; -#if CLIENT - light.Color = IsActive ? lightColor : Color.Transparent; - if (!IsActive) lightBrightness = 0.0f; -#endif + SetLightSourceState(IsActive, lightBrightness); } public override void Update(float deltaTime, Camera cam) { + if (item.AiTarget != null) + { + UpdateAITarget(item.AiTarget); + } UpdateOnActiveEffects(deltaTime); #if CLIENT light.ParentSub = item.Submarine; +#endif if (item.Container != null) { - light.Color = Color.Transparent; + SetLightSourceState(false, 0.0f); return; } +#if CLIENT light.Position = ParentBody != null ? ParentBody.Position : item.Position; #endif PhysicsBody body = ParentBody ?? item.body; - if (body != null) { #if CLIENT @@ -203,9 +195,7 @@ namespace Barotrauma.Items.Components #endif if (!body.Enabled) { -#if CLIENT - light.Color = Color.Transparent; -#endif + SetLightSourceState(false, 0.0f); return; } } @@ -217,7 +207,6 @@ namespace Barotrauma.Items.Components } currPowerConsumption = powerConsumption; - if (Rand.Range(0.0f, 1.0f) < 0.05f && Voltage < Rand.Range(0.0f, MinVoltage)) { #if CLIENT @@ -240,36 +229,21 @@ namespace Barotrauma.Items.Components if (blinkTimer > 0.5f) { -#if CLIENT - light.Color = Color.Transparent; -#endif + SetLightSourceState(false, lightBrightness); } else { -#if CLIENT - light.Color = lightColor * lightBrightness * (1.0f - Rand.Range(0.0f, Flicker)); - light.Range = range; -#endif + SetLightSourceState(true, lightBrightness * (1.0f - Rand.Range(0.0f, flicker))); } - if (item.AiTarget != null) - { - UpdateAITarget(item.AiTarget); - } - } - -#if CLIENT - public override void UpdateBroken(float deltaTime, Camera cam) - { - light.Color = Color.Transparent; - lightBrightness = 0.0f; + + if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; } } - protected override void RemoveComponentSpecific() + public override void UpdateBroken(float deltaTime, Camera cam) { - base.RemoveComponentSpecific(); - light.Remove(); + SetLightSourceState(false, 0.0f); } -#endif + public override bool Use(float deltaTime, Character character = null) { return true; @@ -277,8 +251,6 @@ namespace Barotrauma.Items.Components public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f) { - base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength); - switch (connection.Name) { case "toggle": @@ -300,13 +272,14 @@ namespace Barotrauma.Items.Components private void UpdateAITarget(AITarget target) { - //voltage > minVoltage || powerConsumption <= 0.0f; <- ? - target.Enabled = IsActive; + if (!IsActive) { return; } if (target.MaxSightRange <= 0) { target.MaxSightRange = Range * 5; } - target.SightRange = IsActive ? target.MaxSightRange * lightBrightness : 0; + target.SightRange = target.MaxSightRange * lightBrightness; } + + partial void SetLightSourceState(bool enabled, float brightness); } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs index 26c197604..a04b7c6f8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs @@ -102,7 +102,7 @@ namespace Barotrauma.Items.Components public override void ReceivePowerProbeSignal(Connection connection, Item source, float power) { - if (!IsOn) { return; } + if (!IsOn || item.Condition <= 0.0f) { return; } //we've already received this signal if (lastPowerProbeRecipients.Contains(this)) { return; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index fe184c492..239474284 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs @@ -276,7 +276,7 @@ namespace Barotrauma.Items.Components if (reload > 0.0f) return false; - if (GetAvailablePower() < powerConsumption) + if (GetAvailableBatteryPower() < powerConsumption) { #if CLIENT if (!flashLowPower && character != null && character == Character.Controlled) @@ -410,7 +410,7 @@ namespace Barotrauma.Items.Components character.AIController.SelectTarget(null); } - if (GetAvailablePower() < powerConsumption) + if (GetAvailableBatteryPower() < powerConsumption) { var batteries = item.GetConnectedComponents(); @@ -540,21 +540,6 @@ namespace Barotrauma.Items.Components return false; } - private float GetAvailablePower() - { - var batteries = item.GetConnectedComponents(); - - float availablePower = 0.0f; - foreach (PowerContainer battery in batteries) - { - float batteryPower = Math.Min(battery.Charge*3600.0f, battery.MaxOutPut); - - availablePower += batteryPower; - } - - return availablePower; - } - private void GetAvailablePower(out float availableCharge, out float availableCapacity) { var batteries = item.GetConnectedComponents(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 02340e7d8..618c5e565 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -425,7 +425,7 @@ namespace Barotrauma string[] splitTags = value.Split(','); foreach (string tag in splitTags) { - string[] splitTag = tag.Split(':'); + string[] splitTag = tag.Trim().Split(':'); splitTag[0] = splitTag[0].ToLowerInvariant(); tags.Add(string.Join(":", splitTag)); } @@ -1060,18 +1060,18 @@ namespace Barotrauma return true; } - public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb limb = null, bool isNetworkEvent = false) + public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, Vector2? worldPosition = null) { if (!hasStatusEffectsOfType[(int)type]) { return; } foreach (StatusEffect effect in statusEffectLists[type]) { - ApplyStatusEffect(effect, type, deltaTime, character, limb, isNetworkEvent, false); + ApplyStatusEffect(effect, type, deltaTime, character, limb, useTarget, isNetworkEvent, false, worldPosition); } } readonly List targets = new List(); - public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, bool isNetworkEvent = false, bool checkCondition = true) + public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, bool checkCondition = true, Vector2? worldPosition = null) { if (!isNetworkEvent && checkCondition) { @@ -1110,6 +1110,12 @@ namespace Barotrauma if (targets.Count > 0) { hasTargets = true; } } + if (effect.HasTargetType(StatusEffect.TargetType.UseTarget) && useTarget is ISerializableEntity serializableTarget) + { + hasTargets = true; + targets.Add(serializableTarget); + } + if (!hasTargets) { return; } if (effect.HasTargetType(StatusEffect.TargetType.Hull) && CurrentHull != null) @@ -1125,30 +1131,32 @@ namespace Barotrauma } } - if (effect.HasTargetType(StatusEffect.TargetType.Character)) + if (character != null) { - if (type == ActionType.OnContained && ParentInventory is CharacterInventory characterInventory) + if (effect.HasTargetType(StatusEffect.TargetType.Character)) { - targets.Add(characterInventory.Owner as ISerializableEntity); + if (type == ActionType.OnContained && ParentInventory is CharacterInventory characterInventory) + { + targets.Add(characterInventory.Owner as ISerializableEntity); + } + else + { + targets.Add(character); + } } - else + if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs)) { - targets.Add(character); + targets.AddRange(character.AnimController.Limbs.ToList()); } } - if (effect.HasTargetType(StatusEffect.TargetType.Limb)) { targets.Add(limb); } - if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs)) - { - targets.AddRange(character.AnimController.Limbs.ToList()); - } if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) targets.Add(Container); - effect.Apply(type, deltaTime, this, targets); + effect.Apply(type, deltaTime, this, targets, worldPosition); } @@ -1360,7 +1368,8 @@ namespace Barotrauma { if (transformDirty) { return false; } - Vector2 normal = contact.Manifold.LocalNormal; + contact.GetWorldManifold(out Vector2 normal, out _); + if (contact.FixtureA.Body == f1.Body) { normal = -normal; } float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal); OnCollisionProjSpecific(f1, f2, contact, impact); @@ -1568,7 +1577,7 @@ namespace Barotrauma foreach (StatusEffect effect in connection.Effects) { if (condition <= 0.0f && effect.type != ActionType.OnBroken) { continue; } - if (signal != "0" && !string.IsNullOrEmpty(signal)) { ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false); } + if (signal != "0" && !string.IsNullOrEmpty(signal)) { ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step); } } connection.SendSignal(stepsTaken, signal, source ?? this, sender, power, signalStrength); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs index 8d6fb6e0f..06ff02e92 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -575,6 +575,10 @@ namespace Barotrauma public void ApplyFlowForces(float deltaTime, Item item) { + if (item.body.Mass <= 0.0f) + { + return; + } foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0)) { var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.Position) / 1000, 1f); diff --git a/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs index 8db26524f..47fe3f1f2 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/LinkedSubmarine.cs @@ -180,14 +180,22 @@ namespace Barotrauma } linkedSub.filePath = element.GetAttributeString("filepath", ""); - int[] linkedToIds = element.GetAttributeIntArray("linkedto", new int[0]); + int[] linkedToIds = element.GetAttributeIntArray("linkedto", new int[0]); for (int i = 0; i < linkedToIds.Length; i++) { linkedSub.linkedToID.Add((ushort)linkedToIds[i]); + if (Screen.Selected == GameMain.SubEditorScreen) + { + if (FindEntityByID((ushort)linkedToIds[i]) is MapEntity linked) + { + linkedSub.linkedTo.Add(linked); + } + } } linkedSub.originalLinkedToID = (ushort)element.GetAttributeInt("originallinkedto", 0); linkedSub.originalMyPortID = (ushort)element.GetAttributeInt("originalmyport", 0); - + + return linkedSub.loadSub ? linkedSub : null; } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs index 50b186cd1..bae80827b 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/EntitySpawner.cs @@ -107,11 +107,14 @@ namespace Barotrauma { public readonly Entity Entity; + public readonly UInt16 OriginalID; + public readonly bool Remove = false; public SpawnOrRemove(Entity entity, bool remove) { Entity = entity; + OriginalID = entity.ID; Remove = remove; } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs index 8670798c0..0b352eb25 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs @@ -328,7 +328,16 @@ namespace Barotrauma.Networking } } - public string ServerName; + private string serverName; + public string ServerName + { + get { return serverName; } + set + { + serverName = value; + if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); } + } + } private string serverMessageText; public string ServerMessageText diff --git a/Barotrauma/BarotraumaShared/Source/StatusEffects/PropertyConditional.cs b/Barotrauma/BarotraumaShared/Source/StatusEffects/PropertyConditional.cs index c33a5238e..28397d79d 100644 --- a/Barotrauma/BarotraumaShared/Source/StatusEffects/PropertyConditional.cs +++ b/Barotrauma/BarotraumaShared/Source/StatusEffects/PropertyConditional.cs @@ -177,7 +177,7 @@ namespace Barotrauma string[] readTags = valStr.Split(','); int matches = 0; foreach (string tag in readTags) - if (((Item)target).HasTag(tag)) matches++; + if (target is Item item && item.HasTag(tag)) matches++; //If operator is == then it needs to match everything, otherwise if its != there must be zero matches. return Operator == OperatorType.Equals ? matches >= readTags.Length : matches <= 0; @@ -225,8 +225,7 @@ namespace Barotrauma return success; case ConditionType.SpeciesName: if (target == null) { return Operator == OperatorType.NotEquals; } - Character targetCharacter = target as Character; - if (targetCharacter == null) { return false; } + if (!(target is Character targetCharacter)) { return false; } return (Operator == OperatorType.Equals) == (targetCharacter.SpeciesName == valStr); case ConditionType.EntityType: switch (valStr) diff --git a/Barotrauma/BarotraumaShared/Source/TextManager.cs b/Barotrauma/BarotraumaShared/Source/TextManager.cs index 1cc71d273..16c13bea7 100644 --- a/Barotrauma/BarotraumaShared/Source/TextManager.cs +++ b/Barotrauma/BarotraumaShared/Source/TextManager.cs @@ -12,8 +12,10 @@ namespace Barotrauma //only used if none of the selected content packages contain any text files const string VanillaTextFilePath = "Content/Texts/EnglishVanilla.xml"; + private static readonly object mutex = new object(); + //key = language - private static Dictionary> textPacks = new Dictionary>(); + private static Dictionary> textPacks; private static readonly string[] serverMessageCharacters = new string[] { "~", "[", "]", "=" }; @@ -25,10 +27,16 @@ namespace Barotrauma private set; } - private static readonly HashSet availableLanguages = new HashSet(); + private static HashSet availableLanguages; public static IEnumerable AvailableLanguages { - get { return availableLanguages; } + get + { + lock (mutex) + { + return new HashSet(availableLanguages); + } + } } public static List GetTextFiles() @@ -55,25 +63,29 @@ namespace Barotrauma /// public static string GetTranslatedLanguageName(string language) { - if (!textPacks.ContainsKey(language)) + lock (mutex) { + if (!textPacks.ContainsKey(language)) + { + return language; + } + + foreach (var textPack in textPacks[language]) + { + if (textPack.Language == language) + { + return textPack.TranslatedName; + } + } return language; } - - foreach (var textPack in textPacks[language]) - { - if (textPack.Language == language) - { - return textPack.TranslatedName; - } - } - return language; } public static void LoadTextPacks(IEnumerable selectedContentPackages) { - availableLanguages.Clear(); - textPacks.Clear(); + HashSet newLanguages = new HashSet(); + Dictionary> newTextPacks = new Dictionary>(); + var textFiles = ContentPackage.GetFilesOfType(selectedContentPackages, ContentType.Text); foreach (string file in textFiles) @@ -81,12 +93,12 @@ namespace Barotrauma try { var textPack = new TextPack(file); - availableLanguages.Add(textPack.Language); - if (!textPacks.ContainsKey(textPack.Language)) + newLanguages.Add(textPack.Language); + if (!newTextPacks.ContainsKey(textPack.Language)) { - textPacks.Add(textPack.Language, new List()); + newTextPacks.Add(textPack.Language, new List()); } - textPacks[textPack.Language].Add(textPack); + newTextPacks[textPack.Language].Add(textPack); } catch (Exception e) { @@ -94,7 +106,7 @@ namespace Barotrauma } } - if (textPacks.Count == 0) + if (newTextPacks.Count == 0) { DebugConsole.ThrowError("No text files available in any of the selected content packages. Attempting to find a vanilla English text file..."); if (!File.Exists(VanillaTextFilePath)) @@ -102,9 +114,21 @@ namespace Barotrauma throw new Exception("No text files found in any of the selected content packages or in the default text path!"); } var textPack = new TextPack(VanillaTextFilePath); - availableLanguages.Add(textPack.Language); - textPacks.Add(textPack.Language, new List() { textPack }); + newLanguages.Add(textPack.Language); + newTextPacks.Add(textPack.Language, new List() { textPack }); } + + if (newTextPacks.Count == 0) + { + throw new Exception("Failed to load text packs!"); + } + + lock (mutex) + { + textPacks = newTextPacks; + availableLanguages = newLanguages; + } + Initialized = true; } @@ -112,74 +136,81 @@ namespace Barotrauma { if (string.IsNullOrEmpty(textTag)) { return false; } - if (!textPacks.ContainsKey(Language)) + lock (mutex) { - DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); - Language = "English"; if (!textPacks.ContainsKey(Language)) { - throw new Exception("No text packs available in English!"); + DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); + Language = "English"; + if (!textPacks.ContainsKey(Language)) + { + throw new Exception("No text packs available in English!"); + } + } + foreach (TextPack textPack in textPacks[Language]) + { + if (textPack.Get(textTag) != null) { return true; } } } - foreach (TextPack textPack in textPacks[Language]) - { - if (textPack.Get(textTag) != null) { return true; } - } + return false; } public static string Get(string textTag, bool returnNull = false, string fallBackTag = null) { - if (!textPacks.ContainsKey(Language)) + lock (mutex) { - DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); - Language = "English"; if (!textPacks.ContainsKey(Language)) { - throw new Exception("No text packs available in English!"); - } - } - - foreach (TextPack textPack in textPacks[Language]) - { - string text = textPack.Get(textTag); - if (text != null) { return text; } - } - - if (!string.IsNullOrEmpty(fallBackTag)) - { - foreach (TextPack textPack in textPacks[Language]) - { - string text = textPack.Get(fallBackTag); - if (text != null) { return text; } - } - } - - //if text was not found and we're using a language other than English, see if we can find an English version - //may happen, for example, if a user has selected another language and using mods that haven't been translated to that language - if (Language != "English" && textPacks.ContainsKey("English")) - { - foreach (TextPack textPack in textPacks["English"]) - { - string text = textPack.Get(textTag); - if (text != null) + DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); + Language = "English"; + if (!textPacks.ContainsKey(Language)) { -#if DEBUG - DebugConsole.NewMessage("Text \"" + textTag + "\" not found for the language \"" + Language + "\". Using the English text \"" + text + "\" instead."); -#endif - return text; + throw new Exception("No text packs available in English!"); } } - } - if (returnNull) - { - return null; - } - else - { - DebugConsole.ThrowError("Text \"" + textTag + "\" not found."); - return textTag; + foreach (TextPack textPack in textPacks[Language]) + { + string text = textPack.Get(textTag); + if (text != null) { return text; } + } + + if (!string.IsNullOrEmpty(fallBackTag)) + { + foreach (TextPack textPack in textPacks[Language]) + { + string text = textPack.Get(fallBackTag); + if (text != null) { return text; } + } + } + + //if text was not found and we're using a language other than English, see if we can find an English version + //may happen, for example, if a user has selected another language and using mods that haven't been translated to that language + if (Language != "English" && textPacks.ContainsKey("English")) + { + foreach (TextPack textPack in textPacks["English"]) + { + string text = textPack.Get(textTag); + if (text != null) + { +#if DEBUG + DebugConsole.NewMessage("Text \"" + textTag + "\" not found for the language \"" + Language + "\". Using the English text \"" + text + "\" instead."); +#endif + return text; + } + } + } + + if (returnNull) + { + return null; + } + else + { + DebugConsole.ThrowError("Text \"" + textTag + "\" not found."); + return textTag; + } } } @@ -418,13 +449,16 @@ namespace Barotrauma // And: replacement=formatter(value) public static string GetServerMessage(string serverMessage) { - if (!textPacks.ContainsKey(Language)) + lock (mutex) { - DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); - Language = "English"; if (!textPacks.ContainsKey(Language)) { - throw new Exception("No text packs available in English!"); + DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); + Language = "English"; + if (!textPacks.ContainsKey(Language)) + { + throw new Exception("No text packs available in English!"); + } } } @@ -588,58 +622,64 @@ namespace Barotrauma public static List GetAll(string textTag) { - if (!textPacks.ContainsKey(Language)) + lock (mutex) { - DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); - Language = "English"; if (!textPacks.ContainsKey(Language)) { - throw new Exception("No text packs available in English!"); + DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); + Language = "English"; + if (!textPacks.ContainsKey(Language)) + { + throw new Exception("No text packs available in English!"); + } } - } - List allText; + List allText; - foreach (TextPack textPack in textPacks[Language]) - { - allText = textPack.GetAll(textTag); - if (allText != null) return allText; - } - - //if text was not found and we're using a language other than English, see if we can find an English version - //may happen, for example, if a user has selected another language and using mods that haven't been translated to that language - if (Language != "English" && textPacks.ContainsKey("English")) - { - foreach (TextPack textPack in textPacks["English"]) + foreach (TextPack textPack in textPacks[Language]) { allText = textPack.GetAll(textTag); if (allText != null) return allText; } - } - return null; + //if text was not found and we're using a language other than English, see if we can find an English version + //may happen, for example, if a user has selected another language and using mods that haven't been translated to that language + if (Language != "English" && textPacks.ContainsKey("English")) + { + foreach (TextPack textPack in textPacks["English"]) + { + allText = textPack.GetAll(textTag); + if (allText != null) return allText; + } + } + + return null; + } } public static List> GetAllTagTextPairs() { - if (!textPacks.ContainsKey(Language)) + lock (mutex) { - DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); - Language = "English"; if (!textPacks.ContainsKey(Language)) { - throw new Exception("No text packs available in English!"); + DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); + Language = "English"; + if (!textPacks.ContainsKey(Language)) + { + throw new Exception("No text packs available in English!"); + } } + + List> allText = new List>(); + + foreach (TextPack textPack in textPacks[Language]) + { + allText.AddRange(textPack.GetAllTagTextPairs()); + } + + return allText; } - - List> allText = new List>(); - - foreach (TextPack textPack in textPacks[Language]) - { - allText.AddRange(textPack.GetAllTagTextPairs()); - } - - return allText; } public static string ReplaceGenderPronouns(string text, Gender gender) @@ -689,35 +729,41 @@ namespace Barotrauma #if DEBUG public static void CheckForDuplicates(string lang) { - if (!textPacks.ContainsKey(lang)) + lock (mutex) { - DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!"); - return; - } + if (!textPacks.ContainsKey(lang)) + { + DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!"); + return; + } - int packIndex = 0; - foreach (TextPack textPack in textPacks[lang]) - { - textPack.CheckForDuplicates(packIndex); - packIndex++; + int packIndex = 0; + foreach (TextPack textPack in textPacks[lang]) + { + textPack.CheckForDuplicates(packIndex); + packIndex++; + } } } public static void WriteToCSV() { - string lang = "English"; - - if (!textPacks.ContainsKey(lang)) + lock (mutex) { - DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!"); - return; - } + string lang = "English"; - int packIndex = 0; - foreach (TextPack textPack in textPacks[lang]) - { - textPack.WriteToCSV(packIndex); - packIndex++; + if (!textPacks.ContainsKey(lang)) + { + DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!"); + return; + } + + int packIndex = 0; + foreach (TextPack textPack in textPacks[lang]) + { + textPack.WriteToCSV(packIndex); + packIndex++; + } } } #endif diff --git a/Barotrauma/BarotraumaShared/Source/Utils/TaskPool.cs b/Barotrauma/BarotraumaShared/Source/Utils/TaskPool.cs new file mode 100644 index 000000000..11f1adaf1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/Source/Utils/TaskPool.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Barotrauma +{ + public static class TaskPool + { + private struct TaskAction + { + public Task Task; + public Action OnCompletion; + public object UserData; + } + + private static List taskActions = new List(); + + private static void AddInternal(Task task, Action onCompletion, object userdata) + { + lock (taskActions) + { + taskActions.Add(new TaskAction() { Task = task, OnCompletion = onCompletion, UserData = userdata }); + } + } + + public static void Add(Task task, Action onCompletion) + { + AddInternal(task, (Task t, object obj) => { onCompletion(t); }, null); + } + + public static void Add(Task task, U userdata, Action onCompletion) where U : class + { + AddInternal(task, (Task t, object obj) => { onCompletion(t, (U)obj); }, userdata); + } + + public static void Add(Task task, Action> onCompletion) + { + AddInternal(task, (Task t, object obj) => { onCompletion((Task)t); }, null); + } + + public static void Add(Task task, U userdata, Action, U> onCompletion) where U : class + { + AddInternal(task, (Task t, object obj) => { onCompletion((Task)t, (U)obj); }, userdata); + } + + public static void Update() + { + lock (taskActions) + { + for (int i = 0; i < taskActions.Count; i++) + { + if (taskActions[i].Task.IsCompleted) + { + taskActions[i].OnCompletion?.Invoke(taskActions[i].Task, taskActions[i].UserData); + taskActions.RemoveAt(i); + i--; + } + } + } + } + + public static void PrintTaskExceptions(Task task, string msg) + { + DebugConsole.ThrowError(msg); + foreach (Exception e in task.Exception.InnerExceptions) + { + DebugConsole.ThrowError(e.Message + "\n" + e.StackTrace); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/Source/Utils/ToolBox.cs b/Barotrauma/BarotraumaShared/Source/Utils/ToolBox.cs index 138506c5f..7c84618d9 100644 --- a/Barotrauma/BarotraumaShared/Source/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaShared/Source/Utils/ToolBox.cs @@ -437,5 +437,90 @@ namespace Barotrauma IEnumerable filtered = splitted.SkipWhile(part => part != currentFolder).Skip(1); return string.Join("/", filtered); } + + public static string EscapeCharacters(string str) + { + return str.Replace("\\", "\\\\").Replace("\"", "\\\""); + } + + public static string UnescapeCharacters(string str) + { + string retVal = ""; + for (int i=0;i mergedArgs = new List(); + for (int i=0;i