diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs index a51ce4cc1..627c70255 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs @@ -479,7 +479,7 @@ namespace Barotrauma { CalculateLimbDepths(); var controller = character.SelectedConstruction?.GetComponent(); - if (controller != null && controller.ControlCharacterPose && controller.User == character) + if (controller != null && controller.ControlCharacterPose && controller.User == character && controller.UserInCorrectPosition) { if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index 0223840df..349ef98eb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs @@ -174,7 +174,7 @@ namespace Barotrauma if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen) { - if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null) + if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen) { bool mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && GUI.MouseOn == null; if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked()) @@ -391,7 +391,7 @@ namespace Barotrauma if (npc.CampaignInteractionType == CampaignMode.InteractionType.None || npc.Submarine != character.Submarine || npc.IsDead || npc.IsIncapacitated) { continue; } var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionIcon." + npc.CampaignInteractionType); - GUI.DrawIndicator(spriteBatch, npc.WorldPosition, cam, npc.CurrentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, iconStyle.GetDefaultSprite(), iconStyle.Color); + GUI.DrawIndicator(spriteBatch, npc.WorldPosition, cam, npc.CurrentHull == character.CurrentHull ? 500.0f : 100.0f, iconStyle.GetDefaultSprite(), iconStyle.Color); } foreach (Item item in Item.ItemList) @@ -405,17 +405,17 @@ namespace Barotrauma } if (character.SelectedConstruction != null && - (character.CanInteractWith(Character.Controlled.SelectedConstruction) || Screen.Selected == GameMain.SubEditorScreen)) + (character.CanInteractWith(character.SelectedConstruction) || Screen.Selected == GameMain.SubEditorScreen)) { - character.SelectedConstruction.DrawHUD(spriteBatch, cam, Character.Controlled); + character.SelectedConstruction.DrawHUD(spriteBatch, cam, character); } - if (Character.Controlled.Inventory != null) + if (character.Inventory != null) { - foreach (Item item in Character.Controlled.Inventory.AllItems) + foreach (Item item in character.Inventory.AllItems) { - if (Character.Controlled.HasEquippedItem(item)) + if (character.HasEquippedItem(item)) { - item.DrawHUD(spriteBatch, cam, Character.Controlled); + item.DrawHUD(spriteBatch, cam, character); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index dea65b297..89805c561 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -641,6 +641,8 @@ namespace Barotrauma commands.Add(new Command("wikiimage_sub", "Save an image of the main submarine with a transparent background.", (string[] args) => { if (Submarine.MainSub == null) { return; } + MapEntity.SelectedList.Clear(); + MapEntity.mapEntityList.ForEach(me => me.IsHighlighted = false); WikiImage.Create(Submarine.MainSub); })); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index 6b7265865..9761bf213 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -741,7 +741,6 @@ namespace Barotrauma { float diff = isHorizontal ? scrollToElement.Rect.X - Content.Rect.X : scrollToElement.Rect.Y - Content.Rect.Y; float speed = MathHelper.Clamp(Math.Abs(diff) * 0.1f, 5.0f, 100.0f); - System.Diagnostics.Debug.WriteLine(speed); if (Math.Abs(diff) < speed || GUIScrollBar.DraggingBar != null) { speed = Math.Abs(diff); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs index 5d89dcaa0..86e2a84e3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs @@ -259,7 +259,11 @@ namespace Barotrauma public StrikethroughSettings Strikethrough = null; - public readonly List RichTextData = null; + public List RichTextData + { + get; + private set; + } public bool HasColorHighlight => RichTextData != null; @@ -334,6 +338,12 @@ namespace Barotrauma if (wrappedText == null) { return; } RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(wrappedText, removeExtraSpacing).Y + padding)); } + + public void SetRichText(string richText) + { + RichTextData = Barotrauma.RichTextData.GetRichTextData(richText, out string sanitizedText); + Text = sanitizedText; + } public override void ApplyStyle(GUIComponentStyle componentStyle) { @@ -485,7 +495,7 @@ namespace Barotrauma for (int j = 0; j <= line.Length; j++) { Vector2 lineTextSize = Font.MeasureString(line.Substring(0, j)) * textScale; - Vector2 indexPos = new Vector2(lineTextSize.X + Padding.X, totalTextHeight + Padding.Y - halfHeight); + Vector2 indexPos = new Vector2(lineTextSize.X, totalTextHeight - halfHeight) + TextPos - Origin * textScale; //DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue); positions.Add(new Tuple(indexPos, index + j)); } @@ -498,7 +508,7 @@ namespace Barotrauma for (int i = 0; i <= Text.Length; i++) { Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, i)) * textScale; - Vector2 indexPos = new Vector2(textSize.X + Padding.X, textSize.Y + Padding.Y - halfHeight) + TextPos - Origin * textScale; + Vector2 indexPos = new Vector2(textSize.X, textSize.Y - halfHeight) + TextPos - Origin * textScale; //DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke); positions.Add(new Tuple(indexPos, i)); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index 3771c3a7d..5c97c009d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -1131,7 +1131,9 @@ namespace Barotrauma { if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY) { - var sortResult = itemX.ItemPrefab.Name.CompareTo(itemY.ItemPrefab.Name); + int sortResult = itemX.ItemPrefab.Name != itemY.ItemPrefab.Name ? + itemX.ItemPrefab.Name.CompareTo(itemY.ItemPrefab.Name) : + itemX.ItemPrefab.Identifier.CompareTo(itemY.ItemPrefab.Identifier); if (sortingMethod == SortingMethod.AlphabeticalDesc) { sortResult *= -1; } return sortResult; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index 826ab5f91..fd0d3f782 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -824,25 +824,34 @@ namespace Barotrauma currentUpgradeCategory = category; var entitiesOnSub = submarine.GetItems(true).Where(i => submarine.IsEntityFoundOnThisSub(i, true) && !i.HiddenInGame && i.AllowSwapping && i.Prefab.SwappableItem != null && category.ItemTags.Any(t => i.HasTag(t))).ToList(); - int slotIndex = 0; foreach (Item item in entitiesOnSub) { - slotIndex++; - CreateSwappableItemSlideDown(parent, slotIndex, item, submarine); + CreateSwappableItemSlideDown(parent, item, entitiesOnSub, submarine); } } - private void CreateSwappableItemSlideDown(GUIListBox parent, int slotIndex, Item item, Submarine submarine) + private void CreateSwappableItemSlideDown(GUIListBox parent, Item item, List swappableEntities, Submarine submarine) { if (Campaign == null || submarine == null) { return; } IEnumerable availableReplacements = MapEntityPrefab.List.Where(p => p is ItemPrefab itemPrefab && itemPrefab.SwappableItem != null && - itemPrefab.SwappableItem.CanBeBought && + itemPrefab.SwappableItem.CanBeBought && itemPrefab.SwappableItem.SwapIdentifier.Equals(item.Prefab.SwappableItem.SwapIdentifier, StringComparison.OrdinalIgnoreCase)).Cast(); + var linkedItems = Campaign.UpgradeManager.GetLinkedItemsToSwap(item) ?? new List() { item }; + //create the swap entry only for one of the items (the one with the smallest ID) + if (linkedItems.Min(it => it.ID) < item.ID) { return; } + var currentOrPending = item.PendingItemSwap ?? item.Prefab; + string name = currentOrPending.Name; + string quantityText = ""; + if (linkedItems.Count > 1) + { + quantityText = " " + TextManager.GetWithVariable("campaignstore.quantity", "[amount]", (linkedItems.Count).ToString()); + name += quantityText; + } bool isOpen = false; GUIButton toggleButton = new GUIButton(rectT(1f, 0.1f, parent.Content), text: string.Empty, style: "SlideDown") @@ -850,10 +859,21 @@ namespace Barotrauma UserData = item }; GUILayoutGroup buttonLayout = new GUILayoutGroup(rectT(1f, 1f, toggleButton.Frame), isHorizontal: true); - new GUITextBlock(rectT(0.3f, 1f, buttonLayout), text: TextManager.GetWithVariable("weaponslot", "[number]", slotIndex.ToString()), font: GUI.SubHeadingFont); + + string slotText = ""; + if (linkedItems.Count > 1) + { + slotText = TextManager.GetWithVariable("weaponslot", "[number]", string.Join(", ", linkedItems.Select(it => (swappableEntities.IndexOf(it) + 1).ToString()))); + } + else + { + slotText = TextManager.GetWithVariable("weaponslot", "[number]", (swappableEntities.IndexOf(item) + 1).ToString()); + } + + new GUITextBlock(rectT(0.3f, 1f, buttonLayout), text: slotText, font: GUI.SubHeadingFont); GUILayoutGroup group = new GUILayoutGroup(rectT(0.7f, 1f, buttonLayout), isHorizontal: true) { Stretch = true }; - string title = item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", currentOrPending.Name) : item.Name; + string title = item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : (linkedItems.Count > 1 ? item.Name + quantityText : item.Name); GUITextBlock text = new GUITextBlock(rectT(0.7f, 1f, group), text: title, font: GUI.SubHeadingFont, textAlignment: Alignment.Right, parseRichText: true) { TextColor = GUI.Style.Orange @@ -876,7 +896,7 @@ namespace Barotrauma if (isUninstallPending) { canUninstall = false; } frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), currentOrPending.UpgradePreviewSprite, - TextManager.GetWithVariable(item.PendingItemSwap != null ? "upgrades.pendingitem" : "upgrades.installeditem", "[itemname]", currentOrPending.Name), + TextManager.GetWithVariable(item.PendingItemSwap != null ? "upgrades.pendingitem" : "upgrades.installeditem", "[itemname]", name), currentOrPending.Description, 0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "WeaponUninstallButton")); @@ -915,7 +935,7 @@ namespace Barotrauma bool isPurchased = item.AvailableSwaps.Contains(replacement); - int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation); + int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count; frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description, price, replacement, @@ -931,7 +951,7 @@ namespace Barotrauma { string promptBody = TextManager.GetWithVariables(isPurchased ? "upgrades.itemswappromptbody" : "upgrades.purchaseitemswappromptbody", new[] { "[itemtoinstall]", "[amount]" }, - new[] { replacement.Name, replacement.SwappableItem.GetPrice(Campaign?.Map?.CurrentLocation).ToString() }); + new[] { replacement.Name, (replacement.SwappableItem.GetPrice(Campaign?.Map?.CurrentLocation) * linkedItems.Count).ToString() }); currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () => { if (GameMain.NetworkMember != null) @@ -966,6 +986,7 @@ namespace Barotrauma toggleButton.OnClicked = delegate { + if (Campaign == null) { return false; } isOpen = !isOpen; toggleButton.Selected = !toggleButton.Selected; foreach (GUIFrame frame in frames) @@ -974,9 +995,10 @@ namespace Barotrauma } if (toggleButton.Selected) { + var linkedItems = Campaign.UpgradeManager.GetLinkedItemsToSwap(item); foreach (var itemPreview in itemPreviews) { - itemPreview.Value.OutlineColor = itemPreview.Value.Color = itemPreview.Key == item ? GUI.Style.Orange : previewWhite; + itemPreview.Value.OutlineColor = itemPreview.Value.Color = linkedItems.Contains(itemPreview.Key) ? GUI.Style.Orange : previewWhite; } foreach (GUIComponent otherComponent in toggleButton.Parent.Children) { @@ -1145,9 +1167,9 @@ namespace Barotrauma private void CreateItemTooltip(MapEntity entity) { int slotIndex = -1; - if (currentStoreLayout?.SelectedData is CategoryData categoryData) + if (entity is Item swappableItem && swappableItem.Prefab.SwappableItem != null) { - var entitiesOnSub = Submarine.MainSub.GetItems(true).Where(i => i.Prefab.SwappableItem != null && Submarine.MainSub.IsEntityFoundOnThisSub(i, true) && categoryData.Category.ItemTags.Any(t => i.HasTag(t))).ToList(); + var entitiesOnSub = Submarine.MainSub.GetItems(true).Where(i => i.Prefab.SwappableItem != null && Submarine.MainSub.IsEntityFoundOnThisSub(i, true) && i.Prefab.SwappableItem.SwapIdentifier == swappableItem.Prefab.SwappableItem?.SwapIdentifier).ToList(); slotIndex = entitiesOnSub.IndexOf(entity) + 1; } @@ -1231,6 +1253,8 @@ namespace Barotrauma private void UpdateSubmarinePreview(float deltaTime, GUICustomComponent parent) { + if (Campaign == null) { return; } + if (!parent.Children.Any() || Submarine.MainSub != null && Submarine.MainSub != drawnSubmarine || GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y) { GameMain.GameSession?.SubmarineInfo?.CheckSubsLeftBehind(); @@ -1279,7 +1303,8 @@ namespace Barotrauma { if (selectedUpgradeCategoryLayout != null) { - if (selectedUpgradeCategoryLayout.FindChild(c => c.UserData as Item == HoveredItem, recursive: true) is GUIButton itemElement) + var linkedItems = HoveredItem is Item ? Campaign.UpgradeManager.GetLinkedItemsToSwap((Item)HoveredItem) : new List(); + if (selectedUpgradeCategoryLayout.FindChild(c => c.UserData as Item == HoveredItem || linkedItems.Contains(c.UserData as Item), recursive: true) is GUIButton itemElement) { if (!itemElement.Selected) { itemElement.OnClicked(itemElement, itemElement.UserData); } (itemElement.Parent?.Parent?.Parent as GUIListBox)?.ScrollToElement(itemElement); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Widget.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Widget.cs index 4627be9c8..2fdbdf0d8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Widget.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Widget.cs @@ -115,6 +115,7 @@ namespace Barotrauma if (IsMouseOver || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.PrimaryMouseButtonHeld())) { Hovered?.Invoke(); + System.Diagnostics.Debug.WriteLine("hovered"); if (RequireMouseOn || PlayerInput.PrimaryMouseButtonDown()) { if ((multiselect && !selectedWidgets.Contains(this)) || selectedWidgets.None()) @@ -126,6 +127,7 @@ namespace Barotrauma } else if (selectedWidgets.Contains(this)) { + System.Diagnostics.Debug.WriteLine("selectedWidgets.Contains(this) -> remove"); selectedWidgets.Remove(this); Deselected?.Invoke(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 0e65c1d8c..5cc69eb70 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -140,8 +140,7 @@ namespace Barotrauma msg, ((msgCommand == "r" || msgCommand == "radio") && ChatMessage.CanUseRadio(Character.Controlled)) ? ChatMessageType.Radio : ChatMessageType.Default, Character.Controlled); - var headset = GetHeadset(Character.Controlled, true); - if (headset != null && headset.CanTransmit()) + if (ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent headset)) { Signal s = new Signal(msg, sender: Character.Controlled, source: headset.Item); headset.TransmitSignal(s, sentFromChat: true); @@ -610,17 +609,6 @@ namespace Barotrauma ChatBox.AddMessage(message); } - private WifiComponent GetHeadset(Character character, bool requireEquipped) - { - if (character?.Inventory == null) { return null; } - - var radioItem = character.Inventory.AllItems.FirstOrDefault(it => it.GetComponent() != null); - if (radioItem == null) { return null; } - if (requireEquipped && !character.HasEquippedItem(radioItem)) { return null; } - - return radioItem.GetComponent(); - } - partial void CreateRandomConversation() { if (GameMain.Client != null) @@ -1429,7 +1417,7 @@ namespace Barotrauma } } - if (closestNode != null && closestNode == selectedNode) + if (closestNode != null && closestNode.CanBeFocused && closestNode == selectedNode) { timeSelected += deltaTime; if (timeSelected >= selectionTime) @@ -2485,6 +2473,7 @@ namespace Barotrauma foreach (Order p in Order.PrefabList) { targetComponent = null; + if (p.UseController && itemContext.Components.None(c => c is Controller)) { continue; } if ((p.TargetItems.Length > 0 && (p.TargetItems.Contains(itemContext.Prefab.Identifier) || itemContext.HasTag(p.TargetItems))) || p.TryGetTargetItemComponent(itemContext, out targetComponent)) { @@ -3444,10 +3433,8 @@ namespace Barotrauma bool canIssueOrders = false; if (Character.Controlled?.CurrentHull?.Submarine != null && Character.Controlled.SpeechImpediment < 100.0f) { - WifiComponent radio = GetHeadset(Character.Controlled, true); canIssueOrders = - radio != null && - radio.CanTransmit() && + ChatMessage.CanUseRadio(Character.Controlled) && Character.Controlled?.CurrentHull?.Submarine?.TeamID == Character.Controlled.TeamID && !Character.Controlled.CurrentHull.Submarine.Info.IsWreck; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs index 1e9282575..e7bd89023 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs @@ -224,12 +224,7 @@ namespace Barotrauma { endRoundButton.ToolTip = buttonText; } - if (Character.Controlled?.ViewTarget is Item item) - { - Turret turret = item.GetComponent(); - endRoundButton.RectTransform.ScreenSpaceOffset = turret == null ? Point.Zero : new Point(0, (int)(turret.UIElementHeight * 1.25f)); - } - else if (Character.Controlled?.CharacterHealth?.SuicideButton?.Visible ?? false) + if (Character.Controlled?.CharacterHealth?.SuicideButton?.Visible ?? false) { endRoundButton.RectTransform.ScreenSpaceOffset = new Point(0, Character.Controlled.CharacterHealth.SuicideButton.Rect.Height); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs index 336830891..be0df2d3e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs @@ -552,7 +552,7 @@ namespace Barotrauma if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; } if (!gap.IsRoomToRoom) { - if (!(Character.Controlled.GetEquippedItem("deepdiving") is Item)) { continue; } + if (!(Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item)) { continue; } if (Character.Controlled.IsProtectedFromPressure()) { continue; } if (DisplayHint("divingsuitwarning", extendTextTag: false)) { return; } continue; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index 289c88ec2..cc6ef2654 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -167,6 +167,13 @@ namespace Barotrauma.Items.Components private set; } + [Serialize(0, false)] + public int HudLayer + { + get; + private set; + } + private bool useAlternativeLayout; public bool UseAlternativeLayout { @@ -443,6 +450,8 @@ namespace Barotrauma.Items.Components public virtual void UpdateHUD(Character character, float deltaTime, Camera cam) { } + public virtual void UpdateEditing(float deltaTime) { } + public virtual void CreateEditingHUD(SerializableEntityEditor editor) { } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index 05c6b22bf..4da1bb77e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -44,20 +44,6 @@ namespace Barotrauma.Items.Components private set; } -#if DEBUG - [Editable] -#endif - [Serialize("0.0,0.0", false, description: "The position where the contained items get drawn at (offset from the upper left corner of the sprite in pixels).")] - public Vector2 ItemPos { get; set; } - -#if DEBUG - [Editable] -#endif - [Serialize("0.0,0.0", false, description: "The interval at which the contained items are spaced apart from each other (in pixels).")] - public Vector2 ItemInterval { get; set; } - [Serialize(100, false, description: "How many items are placed in a row before starting a new row.")] - public int ItemsPerRow { get; set; } - /// /// Depth at which the contained sprites are drawn. If not set, the original depth of the item sprites is used. /// @@ -291,7 +277,6 @@ namespace Barotrauma.Items.Components transformedItemPos = Vector2.Transform(transformedItemPos, transform); transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); - transformedItemPos += item.DrawPosition; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs index dd606cad2..8aba265ce 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs @@ -21,6 +21,10 @@ namespace Barotrauma.Items.Components private float[] charWidths; + private float prevScale; + private Rectangle prevRect; + private StringBuilder sb; + private Vector4 padding; [Serialize("0,0,0,0", true, description: "The amount of padding around the text in pixels (left,top,right,bottom).")] @@ -49,7 +53,8 @@ namespace Barotrauma.Items.Components } text = value; - SetDisplayText(value); + SetDisplayText(value); + UpdateScrollingText(); } } @@ -205,9 +210,16 @@ namespace Barotrauma.Items.Components if (!needsScrolling) { return; } scrollAmount -= deltaTime * ScrollSpeed; + UpdateScrollingText(); + } + + private void UpdateScrollingText() + { + if (!scrollable || !needsScrolling) { return; } float currLength = 0; - StringBuilder sb = new StringBuilder(); + sb ??= new StringBuilder(); + sb.Clear(); float textAreaWidth = textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z; for (int i = scrollIndex; i < scrollingText.Length; i++) { @@ -246,10 +258,7 @@ namespace Barotrauma.Items.Components prevScale = item.Scale; prevRect = item.Rect; } - - private float prevScale; - private Rectangle prevRect; - + public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) { if (editing) @@ -267,15 +276,6 @@ namespace Barotrauma.Items.Components item.DrawPosition.X - item.Rect.Width / 2.0f, -(item.DrawPosition.Y + item.Rect.Height / 2.0f)); - Rectangle worldRect = item.WorldRect; - if (worldRect.X > Screen.Selected.Cam.WorldView.Right || - worldRect.Right < Screen.Selected.Cam.WorldView.X || - worldRect.Y < Screen.Selected.Cam.WorldView.Y - Screen.Selected.Cam.WorldView.Height || - worldRect.Y - worldRect.Height > Screen.Selected.Cam.WorldView.Y) - { - return; - } - textBlock.TextDepth = item.SpriteDepth - 0.0001f; textBlock.TextOffset = drawPos - textBlock.Rect.Location.ToVector2() + (editing ? Vector2.Zero : new Vector2(scrollAmount + scrollPadding, 0.0f)); textBlock.DrawManually(spriteBatch); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index 4d7ee5ea1..a2fc0362e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -561,7 +561,8 @@ namespace Barotrauma.Items.Components private bool StartButtonClicked(GUIButton button, object obj) { if (selectedItem == null) { return false; } - if (fabricatedItem == null && !outputContainer.Inventory.CanBePut(selectedItem.TargetItem)) + if (fabricatedItem == null && + !outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition)) { outputSlot.Flash(GUI.Style.Red); return false; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs index 1bedbf2d6..4efc832fc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs @@ -23,6 +23,10 @@ namespace Barotrauma.Items.Components public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character) { + if (DraggingConnected?.Item.Removed ?? false) + { + DraggingConnected = null; + } Rectangle panelRect = panel.GuiFrame.Rect; int x = panelRect.X, y = panelRect.Y; int width = panelRect.Width, height = panelRect.Height; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs index 581fd311e..f28cdf48b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs @@ -304,6 +304,21 @@ namespace Barotrauma.Items.Components } } + public override void UpdateEditing(float deltaTime) + { + if (Screen.Selected == GameMain.SubEditorScreen && item.IsSelected) + { + if (widgets.ContainsKey("maxrotation")) + { + widgets["maxrotation"].Update(deltaTime); + } + if (widgets.ContainsKey("minrotation")) + { + widgets["minrotation"].Update(deltaTime); + } + } + } + public override void UpdateHUD(Character character, float deltaTime, Camera cam) { if (crosshairSprite != null) @@ -469,7 +484,6 @@ namespace Barotrauma.Items.Components { widget.tooltip = "Min: " + (int)MathHelper.ToDegrees(minRotation); widget.DrawPos = GetDrawPos() + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * coneRadius / Screen.Selected.Cam.Zoom * GUI.Scale; - widget.Update(deltaTime); }; }); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index 6c0975078..4fdc6a637 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -1104,6 +1104,8 @@ namespace Barotrauma public static void UpdateDragging() { + DraggingItems.RemoveAll(it => !Character.Controlled.CanInteractWith(it)); + if (DraggingItems.Any() && PlayerInput.PrimaryMouseButtonReleased()) { Character.Controlled.ClearInputs(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 72dd7def7..d675203fd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -22,6 +22,8 @@ namespace Barotrauma private readonly List activeHUDs = new List(); + private readonly List activeEditors = new List(); + public GUIComponentStyle IconStyle { get; private set; } = null; partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType) { @@ -40,7 +42,6 @@ namespace Barotrauma public float LastImpactSoundTime; public const float ImpactSoundInterval = 0.2f; - private bool editingHUDRefreshPending; private float editingHUDRefreshTimer; private ContainedItemSprite activeContainedSprite; @@ -581,14 +582,18 @@ namespace Barotrauma } } - public override void UpdateEditing(Camera cam) + public override void UpdateEditing(Camera cam, float deltaTime) { - if (editingHUD == null || editingHUD.UserData as Item != this || - (editingHUDRefreshPending && editingHUDRefreshTimer <= 0.0f)) + if (editingHUD == null || editingHUD.UserData as Item != this) { editingHUD = CreateEditingHUD(Screen.Selected != GameMain.SubEditorScreen); editingHUDRefreshTimer = 1.0f; } + if (editingHUDRefreshTimer <= 0.0f) + { + activeEditors.ForEach(e => e?.RefreshValues()); + editingHUDRefreshTimer = 1.0f; + } if (Screen.Selected != GameMain.SubEditorScreen) { return; } @@ -606,6 +611,11 @@ namespace Barotrauma if (Character.Controlled == null) { activeHUDs.Clear(); } + foreach (ItemComponent ic in components) + { + ic.UpdateEditing(deltaTime); + } + if (!Linkable) { return; } if (!PlayerInput.KeyDown(Keys.Space)) { return; } @@ -632,7 +642,7 @@ namespace Barotrauma public GUIComponent CreateEditingHUD(bool inGame = false) { - editingHUDRefreshPending = false; + activeEditors.Clear(); int heightScaled = (int)(20 * GUI.Scale); editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this }; @@ -643,6 +653,7 @@ namespace Barotrauma }; var itemEditor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUI.LargeFont) { UserData = this }; + activeEditors.Add(itemEditor); itemEditor.Children.First().Color = Color.Black * 0.7f; if (!inGame) { @@ -782,6 +793,7 @@ namespace Barotrauma var componentEditor = new SerializableEntityEditor(listBox.Content.RectTransform, ic, inGame, showName: !inGame, titleFont: GUI.SubHeadingFont) { UserData = ic }; componentEditor.Children.First().Color = Color.Black * 0.7f; + activeEditors.Add(componentEditor); if (inGame) { @@ -976,7 +988,7 @@ namespace Barotrauma Screen.Selected == GameMain.SubEditorScreen) { GUIComponent prevEditingHUD = editingHUD; - UpdateEditing(cam); + UpdateEditing(cam, deltaTime); editingHUDCreated = editingHUD != null && editingHUD != prevEditingHUD; } @@ -996,7 +1008,7 @@ namespace Barotrauma { if (prefab.IsLinkAllowed(entity.prefab) && entity is Item i) { - if (!i.DisplaySideBySideWhenLinked) continue; + if (!i.DisplaySideBySideWhenLinked) { continue; } activeComponents.AddRange(i.components); } } @@ -1030,6 +1042,8 @@ namespace Barotrauma } } + activeHUDs.Sort((h1, h2) => { return h2.HudLayer.CompareTo(h1.HudLayer); }); + //active HUDs have changed, need to reposition if (!prevActiveHUDs.SequenceEqual(activeHUDs) || editingHUDCreated) { @@ -1132,8 +1146,6 @@ namespace Barotrauma } texts.Add(new ColoredText(nameText, GUI.Style.TextColor, false, false)); - bool noComponentText = true; - if (CampaignInteractionType != CampaignMode.InteractionType.None) { texts.Add(new ColoredText(TextManager.GetWithVariable($"CampaignInteraction.{CampaignInteractionType}", "[key]", GameMain.Config.KeyBindText(InputType.Use)), Color.Cyan, false, false)); @@ -1159,7 +1171,6 @@ namespace Barotrauma } } texts.Add(new ColoredText(ic.DisplayMsg, color, false, false)); - noComponentText = false; } } if (PlayerInput.IsShiftDown() && CrewManager.DoesItemHaveContextualOrders(this)) @@ -1295,7 +1306,6 @@ namespace Barotrauma break; case NetEntityEvent.Type.ChangeProperty: ReadPropertyChange(msg, false); - editingHUDRefreshPending = true; break; case NetEntityEvent.Type.Upgrade: string identifier = msg.ReadString(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index a89cb1d2f..2138e1109 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -101,7 +101,7 @@ namespace Barotrauma return editingHUD; } - public override void UpdateEditing(Camera cam) + public override void UpdateEditing(Camera cam, float deltaTime) { if (editingHUD == null || editingHUD.UserData as Hull != this) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs index ebf26774d..8028f686a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs @@ -23,6 +23,14 @@ namespace Barotrauma public LevelWallVertexBuffer(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Texture2D wallTexture, Texture2D edgeTexture, Color color) { + if (wallVertices.Length == 0) + { + throw new ArgumentException("Failed to instantiate a LevelWallVertexBuffer (no wall vertices)."); + } + if (wallVertices.Length == 0) + { + throw new ArgumentException("Failed to instantiate a LevelWallVertexBuffer (no wall edge vertices)."); + } this.wallVertices = LevelRenderer.GetColoredVertices(wallVertices, color); WallBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, wallVertices.Length, BufferUsage.WriteOnly); WallBuffer.SetData(this.wallVertices); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs index f02802fb9..2e44b1875 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs @@ -617,14 +617,10 @@ namespace Barotrauma.Lights private List FindRaycastHits() { - if (!CastShadows) - { - return null; - } - if (Range < 1.0f || Color.A < 1) { return null; } + if (!CastShadows || Range < 1.0f || Color.A < 1) { return null; } Vector2 drawPos = position; - if (ParentSub != null) drawPos += ParentSub.DrawPosition; + if (ParentSub != null) { drawPos += ParentSub.DrawPosition; } var hulls = new List(); foreach (ConvexHullList chList in hullsInRange) @@ -826,13 +822,13 @@ namespace Barotrauma.Lights Vector2 dirNormal = new Vector2(-dir.Y, dir.X) * 3; //do two slightly offset raycasts to hit the segment itself and whatever's behind it - Pair intersection1 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 - dirNormal, visibleSegments); - Pair intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments); + var intersection1 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 - dirNormal, visibleSegments); + var intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments); - if (intersection1.First < 0) return null; - if (intersection2.First < 0) return null; - Segment seg1 = visibleSegments[intersection1.First]; - Segment seg2 = visibleSegments[intersection2.First]; + if (intersection1.index < 0) return null; + if (intersection2.index < 0) return null; + Segment seg1 = visibleSegments[intersection1.index]; + Segment seg2 = visibleSegments[intersection2.index]; bool isPoint1 = MathUtils.LineToPointDistanceSquared(seg1.Start.WorldPos, seg1.End.WorldPos, p.WorldPos) < 25.0f; bool isPoint2 = MathUtils.LineToPointDistanceSquared(seg2.Start.WorldPos, seg2.End.WorldPos, p.WorldPos) < 25.0f; @@ -849,12 +845,12 @@ namespace Barotrauma.Lights hullList.IsHidden.Remove(seg2.ConvexHull); } } - else if (intersection1.First != intersection2.First) + else if (intersection1.index != intersection2.index) { //the raycasts landed on different segments //we definitely want to generate new geometry here - output.Add(isPoint1 ? p.WorldPos : intersection1.Second); - output.Add(isPoint2 ? p.WorldPos : intersection2.Second); + output.Add(isPoint1 ? p.WorldPos : intersection1.pos); + output.Add(isPoint2 ? p.WorldPos : intersection2.pos); foreach (ConvexHullList hullList in hullsInRange) { @@ -884,7 +880,7 @@ namespace Barotrauma.Lights return output; } - private Pair RayCast(Vector2 rayStart, Vector2 rayEnd, List segments) + private (int index, Vector2 pos) RayCast(Vector2 rayStart, Vector2 rayEnd, List segments) { Vector2? closestIntersection = null; int segment = -1; @@ -943,8 +939,7 @@ namespace Barotrauma.Lights } } - Pair retVal = new Pair(segment, closestIntersection == null ? rayEnd : (Vector2)closestIntersection); - return retVal; + return (segment, closestIntersection == null ? rayEnd : (Vector2)closestIntersection); } @@ -1281,11 +1276,6 @@ namespace Barotrauma.Lights { if (Range < 1.0f || Color.A < 1 || CurrentBrightness <= 0.0f) { return; } - if (CastShadows) - { - CheckHullsInRange(); - } - //if the light doesn't cast shadows, we can simply render the texture without having to calculate the light volume if (!CastShadows) { @@ -1298,16 +1288,27 @@ namespace Barotrauma.Lights float scale = Range / (currentTexture.Width / 2.0f); Vector2 drawPos = position; - if (ParentSub != null) drawPos += ParentSub.DrawPosition; + if (ParentSub != null) { drawPos += ParentSub.DrawPosition; } drawPos.Y = -drawPos.Y; spriteBatch.Draw(currentTexture, drawPos, null, Color.Multiply(CurrentBrightness), -rotation, center, scale, SpriteEffects.None, 1); return; } + CheckHullsInRange(); + if (NeedsRecalculation) { var verts = FindRaycastHits(); + if (verts == null) + { +#if DEBUG + DebugConsole.ThrowError($"Failed to generate vertices for a light source. Range: {Range}, color: {Color}, brightness: {CurrentBrightness}, parent: {ParentBody?.UserData ?? "Unknown"}"); +#endif + Enabled = false; + return; + } + CalculateLightVertices(verts); lastRecalculationTime = (float)Timing.TotalTime; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs index d3e649c98..4c96fb6a2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs @@ -55,23 +55,23 @@ namespace Barotrauma GUI.DrawLine(spriteBatch, pos + Vector2.UnitX * 50.0f, pos - Vector2.UnitX * 50.0f, color * alpha, 0.0f, 5); } - public override void UpdateEditing(Camera cam) + public override void UpdateEditing(Camera cam, float deltaTime) { if (editingHUD == null || editingHUD.UserData as LinkedSubmarine != this) { editingHUD = CreateEditingHUD(); } - editingHUD.UpdateManually((float)Timing.Step); + editingHUD.UpdateManually(deltaTime); - if (!PlayerInput.PrimaryMouseButtonClicked() || !PlayerInput.KeyDown(Keys.Space)) return; + if (!PlayerInput.PrimaryMouseButtonClicked() || !PlayerInput.KeyDown(Keys.Space)) { return; } Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); foreach (MapEntity entity in mapEntityList) { - if (entity == this || !entity.IsHighlighted || !(entity is Item) || !entity.IsMouseOn(position)) continue; - if (((Item)entity).GetComponent() == null) continue; + if (entity == this || !entity.IsHighlighted || !(entity is Item) || !entity.IsMouseOn(position)) { continue; } + if (((Item)entity).GetComponent() == null) { continue; } if (linkedTo.Contains(entity)) { linkedTo.Remove(entity); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index 2627bec37..9fad6fc1c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs @@ -838,9 +838,9 @@ namespace Barotrauma public static List FilteredSelectedList { get; private set; } = new List(); - public static void UpdateEditor(Camera cam) + public static void UpdateEditor(Camera cam, float deltaTime) { - if (highlightedListBox != null) highlightedListBox.UpdateManually((float)Timing.Step); + if (highlightedListBox != null) { highlightedListBox.UpdateManually(deltaTime); } if (editingHUD != null) { @@ -865,7 +865,7 @@ namespace Barotrauma var first = FilteredSelectedList.FirstOrDefault(); if (first != null) { - first.UpdateEditing(cam); + first.UpdateEditing(cam, deltaTime); if (first.ResizeHorizontal || first.ResizeVertical) { first.UpdateResizing(cam); @@ -1017,7 +1017,7 @@ namespace Barotrauma if (editingHUD != null && editingHUD.UserData == this) editingHUD.AddToGUIUpdateList(); } - public virtual void UpdateEditing(Camera cam) { } + public virtual void UpdateEditing(Camera cam, float deltaTime) { } protected static void PositionEditingHUD() { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs index 16fedb53c..219b290af 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs @@ -83,7 +83,7 @@ namespace Barotrauma convexHulls.Add(h); } - public override void UpdateEditing(Camera cam) + public override void UpdateEditing(Camera cam, float deltaTime) { if (editingHUD == null || editingHUD.UserData as Structure != this) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs index 067da4a73..68e65bc1f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs @@ -53,8 +53,8 @@ namespace Barotrauma } } - private Dictionary hullCollections; - private List doors; + private readonly Dictionary hullCollections; + private readonly List doors; private static SubmarinePreview instance = null; @@ -104,7 +104,7 @@ namespace Barotrauma (spriteBatch, component) => { camera.UpdateTransform(interpolate: true, updateListener: false); Rectangle drawRect = new Rectangle(component.Rect.X + 1, component.Rect.Y + 1, component.Rect.Width - 2, component.Rect.Height - 2); - RenderSubmarine(spriteBatch, drawRect); + RenderSubmarine(spriteBatch, drawRect, component); }, (deltaTime, component) => { bool isMouseOnComponent = GUI.MouseOn == component; @@ -121,6 +121,10 @@ namespace Barotrauma { specsContainer.Visible = GUI.IsMouseOn(titleText); } + if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) + { + Dispose(); + } }); var topContainer = new GUIFrame(new RectTransform(new Vector2(1f, 0.07f), innerPadded.RectTransform, Anchor.TopLeft), style: null) @@ -225,6 +229,7 @@ namespace Barotrauma foreach (var subElement in submarineInfo.SubmarineElement.Elements()) { + if (subElement.GetAttributeBool("hiddeningame", false)) { continue; } switch (subElement.Name.LocalName.ToLowerInvariant()) { case "item": @@ -240,8 +245,7 @@ namespace Barotrauma string identifier = subElement.GetAttributeString("roomname", "").ToLowerInvariant(); if (!string.IsNullOrEmpty(identifier)) { - HullCollection hullCollection = null; - if (!hullCollections.TryGetValue(identifier, out hullCollection)) + if (!hullCollections.TryGetValue(identifier, out HullCollection hullCollection)) { hullCollection = new HullCollection(identifier); hullCollections.Add(identifier, hullCollection); @@ -476,12 +480,14 @@ namespace Barotrauma } } - var transformedBarrelPos = MathUtils.RotatePointAroundTarget( - subElement.GetAttributeVector2("barrelpos", Vector2.Zero) * scale, - new Vector2(rect.Width / 2, rect.Height / 2), + Vector2 barrelPos = subElement.GetAttributeVector2("barrelpos", Vector2.Zero); + Vector2 relativeBarrelPos = barrelPos * prefab.Scale - new Vector2(rect.Width / 2, rect.Height / 2); + var transformedBarrelPos = MathUtils.RotatePoint( + relativeBarrelPos, MathHelper.ToRadians(rotation)); - Vector2 drawPos = new Vector2(rect.X + transformedBarrelPos.X, rect.Y - transformedBarrelPos.Y); + float relativeScale = scale / prefab.Scale; + Vector2 drawPos = new Vector2(rect.X + rect.Width * relativeScale / 2 + transformedBarrelPos.X * relativeScale, rect.Y - rect.Height * relativeScale / 2 - transformedBarrelPos.Y * relativeScale); drawPos.Y = -drawPos.Y; railSprite?.Draw(spriteRecorder, @@ -491,7 +497,7 @@ namespace Barotrauma SpriteEffects.None, depth + (railSprite.Depth - prefab.sprite.Depth)); barrelSprite?.Draw(spriteRecorder, - drawPos - new Vector2((float)Math.Cos(MathHelper.ToRadians(rotation)), (float)Math.Sin(MathHelper.ToRadians(rotation))) * scale, + drawPos, color, rotation + MathHelper.PiOver2, scale, SpriteEffects.None, depth + (barrelSprite.Depth - prefab.sprite.Depth)); @@ -564,7 +570,7 @@ namespace Barotrauma } } - private void RenderSubmarine(SpriteBatch spriteBatch, Rectangle scissorRectangle) + private void RenderSubmarine(SpriteBatch spriteBatch, Rectangle scissorRectangle, GUIComponent component) { if (spriteRecorder == null) { return; } @@ -605,11 +611,13 @@ namespace Barotrauma foreach (var hullCollection in hullCollections.Values) { bool mouseOver = false; - - foreach (var rect in hullCollection.Rects) + if (GUI.MouseOn == null || GUI.MouseOn == component) { - mouseOver = rect.Contains(mousePos); - if (mouseOver) { break; } + foreach (var rect in hullCollection.Rects) + { + mouseOver = rect.Contains(mousePos); + if (mouseOver) { break; } + } } foreach (var rect in hullCollection.Rects) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs index 8d9bf6343..85da43a34 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs @@ -149,7 +149,7 @@ namespace Barotrauma } } - public override void UpdateEditing(Camera cam) + public override void UpdateEditing(Camera cam, float deltaTime) { if (editingHUD == null || editingHUD.UserData != this) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs index 3d9cad979..7882cc77b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs @@ -318,9 +318,10 @@ namespace Barotrauma if (MapGenerationParams.Instance.RadiationParams != null) { + bool prevRadiationToggleEnabled = EnableRadiationToggle?.Selected ?? true; EnableRadiationToggle = new GUITickBox(new RectTransform(new Vector2(0.3f, 0.3f), CampaignSettingsContent.RectTransform), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font) { - Selected = true, + Selected = prevRadiationToggleEnabled, ToolTip = TextManager.Get("campaignoption.enableradiation.tooltip") }; } @@ -340,7 +341,9 @@ namespace Barotrauma return true; } }; - MaxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), CampaignSettings.DefaultMaxMissionCount.ToString(), textAlignment: Alignment.Center, style: "GUITextBox"); + + string prevMaxMissionCountText = MaxMissionCountText?.Text ?? CampaignSettings.DefaultMaxMissionCount.ToString(); + MaxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), prevMaxMissionCountText, textAlignment: Alignment.Center, style: "GUITextBox"); maxMissionCountButtons[1] = new GUIButton(new RectTransform(new Vector2(0.15f, 0.8f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleRight") { OnClicked = (button, obj) => @@ -806,11 +809,12 @@ namespace Barotrauma UserData = "savefileframe" }; - new GUITextBlock(new RectTransform(new Vector2(1, 0.2f), saveFileFrame.RectTransform, Anchor.TopCenter) + var titleText = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.2f), saveFileFrame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0, 0.05f) }, Path.GetFileNameWithoutExtension(fileName), font: GUI.LargeFont, textAlignment: Alignment.Center); + titleText.Text = ToolBox.LimitString(titleText.Text, titleText.Font, titleText.Rect.Width); var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.5f), saveFileFrame.RectTransform, Anchor.Center) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index 3c55ebd3e..947077154 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -22,6 +22,7 @@ namespace Barotrauma private GUIListBox missionList; private readonly List missionTickBoxes = new List(); + private readonly List missionRewardTexts = new List(); private bool hasMaxMissions; @@ -368,6 +369,7 @@ namespace Barotrauma public void SelectLocation(Location location, LocationConnection connection) { missionTickBoxes.Clear(); + missionRewardTexts.Clear(); locationInfoPanel.ClearChildren(); //don't select the map panel if we're looking at some other tab if (selectedTab == CampaignMode.InteractionType.Map) @@ -524,6 +526,12 @@ namespace Barotrauma Campaign.Map.CurrentLocation.DeselectMission(mission); } + foreach (GUITextBlock rewardText in missionRewardTexts) + { + Mission otherMission = rewardText.UserData as Mission; + rewardText.SetRichText(otherMission.GetMissionRewardText(Submarine.MainSub)); + } + UpdateMaxMissions(connection.OtherLocation(currentDisplayLocation)); if ((Campaign is MultiPlayerCampaign multiPlayerCampaign) && !multiPlayerCampaign.SuppressStateSending && @@ -567,7 +575,11 @@ namespace Barotrauma //spacing new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform) { MinSize = new Point(0, GUI.IntScale(10)) }, style: null); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.GetMissionRewardText(Submarine.MainSub), wrap: true, parseRichText: true); + var rewardText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.GetMissionRewardText(Submarine.MainSub), wrap: true, parseRichText: true) + { + UserData = mission + }; + missionRewardTexts.Add(rewardText); string reputationText = mission.GetReputationRewardText(mission.Locations[0]); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText, wrap: true, parseRichText: true); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs index ac7c804b9..75bbf5cfc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs @@ -119,6 +119,7 @@ namespace Barotrauma addValueDropdown.AddItem(nameof(LimbType), typeof(LimbType)); addValueDropdown.AddItem(nameof(ReputationAction.ReputationType), typeof(ReputationAction.ReputationType)); addValueDropdown.AddItem(nameof(SpawnAction.SpawnLocationType), typeof(SpawnAction.SpawnLocationType)); + addValueDropdown.AddItem(nameof(CharacterTeamType), typeof(CharacterTeamType)); loadButton.OnClicked += (button, o) => Load(loadDropdown.SelectedData as EventPrefab); addActionButton.OnClicked += (button, o) => AddAction(addActionDropdown.SelectedData as Type); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs index 33ad81659..e40a03ece 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs @@ -1089,6 +1089,7 @@ namespace Barotrauma itemContentPackage = ContentPackage.CreatePackage(sub.Name, Path.Combine(destinationFolder, SteamManager.MetadataFileName), corePackage: false); SteamManager.CreateWorkshopItemStaging(itemContentPackage, out itemEditor); + bool fileMoved = false; string submarineDir = Path.GetDirectoryName(sub.FilePath); if (submarineDir != Path.GetDirectoryName(destinationFolder)) { @@ -1097,14 +1098,18 @@ namespace Barotrauma { File.Move(sub.FilePath, destinationPath); } + fileMoved = true; sub.FilePath = destinationPath; } itemContentPackage.AddFile(sub.FilePath, ContentType.Submarine); itemContentPackage.Name = sub.Name; itemContentPackage.Save(itemContentPackage.Path); - //ContentPackage.List.Add(itemContentPackage); - //GameMain.Config.SelectContentPackage(itemContentPackage); + + if (fileMoved) + { + GameMain.Config.EnableRegularPackage(itemContentPackage); + } itemEditor = itemEditor?.WithTitle(sub.Name).WithTag("Submarine").WithDescription(sub.Description); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 92cc56d16..f14529247 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -4696,7 +4696,7 @@ namespace Barotrauma CloseItem(); } } - MapEntity.UpdateEditor(cam); + MapEntity.UpdateEditor(cam, (float)deltaTime); } entityMenuOpenState = entityMenuOpen && !WiringMode ? diff --git a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs index 19a61d2a6..986afef6d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs @@ -10,10 +10,10 @@ namespace Barotrauma { class SerializableEntityEditor : GUIComponent { - private int elementHeight; - private GUILayoutGroup layoutGroup; - private float inputFieldWidth = 0.5f; - private float largeInputFieldWidth = 0.8f; + private readonly int elementHeight; + private readonly GUILayoutGroup layoutGroup; + private readonly float inputFieldWidth = 0.5f; + private readonly float largeInputFieldWidth = 0.8f; #if DEBUG public static List MissingLocalizations = new List(); #endif @@ -23,6 +23,8 @@ namespace Barotrauma public static DateTime NextCommandPush; public static Tuple CommandBuffer; + private Action refresh; + public int ContentHeight { get @@ -312,6 +314,11 @@ namespace Barotrauma Recalculate(); } + public void RefreshValues() + { + refresh?.Invoke(); + } + public void Recalculate() => RectTransform.Resize(new Point(RectTransform.NonScaledSize.X, ContentHeight)); public GUIComponent CreateNewField(SerializableProperty property, ISerializableEntity entity) @@ -459,6 +466,10 @@ namespace Barotrauma return true; } }; + refresh += () => + { + propertyTickBox.Selected = (bool)property.GetValue(entity); + }; if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, new GUIComponent[] { propertyTickBox }); } return propertyTickBox; } @@ -480,7 +491,7 @@ namespace Barotrauma ToolTip = toolTip, Font = GUI.SmallFont }; - field = numberInput as GUIComponent; + field = numberInput; } else { @@ -499,7 +510,11 @@ namespace Barotrauma TrySendNetworkUpdate(entity, property); } }; - field = numberInput as GUIComponent; + refresh += () => + { + if (!numberInput.TextBox.Selected) { numberInput.IntValue = (int)property.GetValue(entity); } + }; + field = numberInput; } if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, new GUIComponent[] { field }); } return frame; @@ -538,6 +553,10 @@ namespace Barotrauma TrySendNetworkUpdate(entity, property); } }; + refresh += () => + { + if (!numberInput.TextBox.Selected) { numberInput.FloatValue = (float)property.GetValue(entity); } + }; if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, new GUIComponent[] { numberInput }); } return frame; } @@ -567,6 +586,10 @@ namespace Barotrauma } return true; }; + refresh += () => + { + if (!enumDropDown.Dropped) { enumDropDown.SelectItem(property.GetValue(entity)); } + }; if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, new GUIComponent[] { enumDropDown }); } return frame; } @@ -635,6 +658,10 @@ namespace Barotrauma propertyBox.OnDeselected += (textBox, keys) => OnApply(textBox); propertyBox.OnEnterPressed += (box, text) => OnApply(box); + refresh += () => + { + if (!propertyBox.Selected) { propertyBox.Text = (string)property.GetValue(entity); } + }; bool OnApply(GUITextBox textBox) { @@ -730,6 +757,15 @@ namespace Barotrauma }; fields[i] = numberInput; } + refresh += () => + { + if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected)) + { + Point value = (Point)property.GetValue(entity); + ((GUINumberInput)fields[0]).IntValue = value.X; + ((GUINumberInput)fields[1]).IntValue = value.Y; + } + }; frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y)); if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; @@ -791,6 +827,15 @@ namespace Barotrauma }; fields[i] = numberInput; } + refresh += () => + { + if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected)) + { + Vector2 value = (Vector2)property.GetValue(entity); + ((GUINumberInput)fields[0]).FloatValue = value.X; + ((GUINumberInput)fields[1]).FloatValue = value.Y; + } + }; frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y)); if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; @@ -857,6 +902,16 @@ namespace Barotrauma }; fields[i] = numberInput; } + refresh += () => + { + if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected)) + { + Vector3 value = (Vector3)property.GetValue(entity); + ((GUINumberInput)fields[0]).FloatValue = value.X; + ((GUINumberInput)fields[1]).FloatValue = value.Y; + ((GUINumberInput)fields[2]).FloatValue = value.Z; + } + }; frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y)); if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; @@ -927,6 +982,17 @@ namespace Barotrauma }; fields[i] = numberInput; } + refresh += () => + { + if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected)) + { + Vector4 value = (Vector4)property.GetValue(entity); + ((GUINumberInput)fields[0]).FloatValue = value.X; + ((GUINumberInput)fields[1]).FloatValue = value.Y; + ((GUINumberInput)fields[2]).FloatValue = value.Z; + ((GUINumberInput)fields[3]).FloatValue = value.W; + } + }; frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y)); if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; @@ -993,13 +1059,13 @@ namespace Barotrauma { Color newVal = (Color)property.GetValue(entity); if (comp == 0) - newVal.R = (byte)(numInput.IntValue); + newVal.R = (byte)numInput.IntValue; else if (comp == 1) - newVal.G = (byte)(numInput.IntValue); + newVal.G = (byte)numInput.IntValue; else if (comp == 2) - newVal.B = (byte)(numInput.IntValue); + newVal.B = (byte)numInput.IntValue; else - newVal.A = (byte)(numInput.IntValue); + newVal.A = (byte)numInput.IntValue; if (SetPropertyValue(property, entity, newVal)) { @@ -1010,6 +1076,17 @@ namespace Barotrauma colorBox.Color = colorBox.HoverColor = colorBox.PressedColor = colorBox.SelectedTextColor = (Color)property.GetValue(entity); fields[i] = numberInput; } + refresh += () => + { + if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected)) + { + Color value = (Color)property.GetValue(entity); + ((GUINumberInput)fields[0]).IntValue = value.R; + ((GUINumberInput)fields[1]).IntValue = value.G; + ((GUINumberInput)fields[2]).IntValue = value.B; + ((GUINumberInput)fields[3]).IntValue = value.A; + } + }; frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y)); if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; @@ -1072,6 +1149,17 @@ namespace Barotrauma }; fields[i] = numberInput; } + refresh += () => + { + if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected)) + { + Rectangle value = (Rectangle)property.GetValue(entity); + ((GUINumberInput)fields[0]).IntValue = value.X; + ((GUINumberInput)fields[1]).IntValue = value.Y; + ((GUINumberInput)fields[2]).IntValue = value.Width; + ((GUINumberInput)fields[3]).IntValue = value.Height; + } + }; if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundBuffer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundBuffer.cs index 8cbcc5b37..79e00f0f1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundBuffer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundBuffer.cs @@ -9,14 +9,14 @@ namespace Barotrauma.Sounds { public class SoundBuffers : IDisposable { - private static HashSet bufferPool = new HashSet(); + private static readonly HashSet bufferPool = new HashSet(); #if OSX public const int MaxBuffers = 400; //TODO: check that this value works for macOS #else public const int MaxBuffers = 32000; #endif public static int BuffersGenerated { get; private set; } = 0; - private Sound sound; + private readonly Sound sound; public uint AlBuffer { get; private set; } = 0; public uint AlMuffledBuffer { get; private set; } = 0; @@ -24,55 +24,73 @@ namespace Barotrauma.Sounds public SoundBuffers(Sound sound) { this.sound = sound; } public void Dispose() { - if (AlBuffer != 0) { bufferPool.Add(AlBuffer); } - if (AlMuffledBuffer != 0) { bufferPool.Add(AlMuffledBuffer); } + if (AlBuffer != 0) + { + lock (bufferPool) + { + bufferPool.Add(AlBuffer); + } + } + if (AlMuffledBuffer != 0) + { + lock (bufferPool) + { + bufferPool.Add(AlMuffledBuffer); + } + } AlBuffer = 0; AlMuffledBuffer = 0; } public static void ClearPool() { - bufferPool.ForEach(b => Al.DeleteBuffer(b)); - bufferPool.Clear(); + lock (bufferPool) + { + bufferPool.ForEach(b => Al.DeleteBuffer(b)); + bufferPool.Clear(); + } BuffersGenerated = 0; } public bool RequestAlBuffers() { if (AlBuffer != 0) { return false; } - int alError = 0; - while (bufferPool.Count < 2 && BuffersGenerated < MaxBuffers) + int alError; + lock (bufferPool) { - Al.GenBuffer(out uint newBuffer); - alError = Al.GetError(); - if (alError != Al.NoError) + while (bufferPool.Count < 2 && BuffersGenerated < MaxBuffers) { - DebugConsole.AddWarning($"Error when generating sound buffer: {Al.GetErrorString(alError)}. {BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated."); - BuffersGenerated = MaxBuffers; - } - else if (!Al.IsBuffer(newBuffer)) - { - DebugConsole.AddWarning($"Error when generating sound buffer: result is not a valid buffer. {BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated."); - BuffersGenerated = MaxBuffers; - } - else - { - bufferPool.Add(newBuffer); - BuffersGenerated++; - if (BuffersGenerated >= MaxBuffers) + Al.GenBuffer(out uint newBuffer); + alError = Al.GetError(); + if (alError != Al.NoError) { - DebugConsole.AddWarning($"{BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated."); + DebugConsole.AddWarning($"Error when generating sound buffer: {Al.GetErrorString(alError)}. {BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated."); + BuffersGenerated = MaxBuffers; + } + else if (!Al.IsBuffer(newBuffer)) + { + DebugConsole.AddWarning($"Error when generating sound buffer: result is not a valid buffer. {BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated."); + BuffersGenerated = MaxBuffers; + } + else + { + bufferPool.Add(newBuffer); + BuffersGenerated++; + if (BuffersGenerated >= MaxBuffers) + { + DebugConsole.AddWarning($"{BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated."); + } } } - } - if (bufferPool.Count >= 2) - { - AlBuffer = bufferPool.First(); - bufferPool.Remove(AlBuffer); - AlMuffledBuffer = bufferPool.First(); - bufferPool.Remove(AlMuffledBuffer); - return true; + if (bufferPool.Count >= 2) + { + AlBuffer = bufferPool.First(); + bufferPool.Remove(AlBuffer); + AlMuffledBuffer = bufferPool.First(); + bufferPool.Remove(AlMuffledBuffer); + return true; + } } //can't generate any more OpenAL buffers! we'll have to steal a buffer from someone... @@ -112,7 +130,6 @@ namespace Barotrauma.Sounds throw new Exception(sound.Filename + " has an invalid muffled buffer!"); } - return true; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs index d5defd0dd..b967865ac 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs @@ -167,10 +167,10 @@ namespace Barotrauma.Sounds public CategoryModifier(int gainMultiplierIndex, float gain, bool muffle) { Muffle = muffle; - GainMultipliers = new float[gainMultiplierIndex+1]; - for (int i=0;i soundElements = new List(); foreach (ContentFile soundFile in soundFiles) diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 92e2f7fe6..a87c1c495 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.14.6.0 + 0.1400.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 337e40141..abc93b9cf 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.14.6.0 + 0.1400.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma @@ -94,7 +94,7 @@ - + @@ -104,6 +104,16 @@ + + + + + + + + + + diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index a5333c7b6..a7c2129df 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.14.6.0 + 0.1400.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index bcbb4b32d..9f804b322 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.14.6.0 + 0.1400.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index a73a20ca2..acd280e75 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.14.6.0 + 0.1400.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs index 24523ad42..20f63ddd9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs @@ -6,6 +6,11 @@ namespace Barotrauma { public bool HasSpawned; + public bool HasItemData + { + get { return itemData != null; } + } + partial void InitProjSpecific(Client client) { ClientEndPoint = client.Connection.EndPointString; diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 49abc7448..3996fdbeb 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -172,11 +172,25 @@ namespace Barotrauma //refresh the character data of clients who are still in the server foreach (Client c in GameMain.Server.ConnectedClients) { + if (c.HasSpawned && c.CharacterInfo != null && c.CharacterInfo.CauseOfDeath != null && c.CharacterInfo.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) + { + //the client has opted to spawn this round with Reaper's Tax + if (c.WaitForNextRoundRespawn.HasValue && !c.WaitForNextRoundRespawn.Value) + { + c.CharacterInfo.StartItemsGiven = false; + characterData.RemoveAll(cd => cd.MatchesClient(c)); + characterData.Add(new CharacterCampaignData(c, giveRespawnPenaltyAffliction: true)); + continue; + } + } if (c.Character?.Info == null) { continue; } - if (c.Character.IsDead && c.Character.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) { continue; } + if (c.Character.IsDead && c.Character.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) + { + continue; + } c.CharacterInfo = c.Character.Info; characterData.RemoveAll(cd => cd.MatchesClient(c)); - characterData.Add(new CharacterCampaignData(c)); + characterData.Add(new CharacterCampaignData(c)); } //refresh the character data of clients who aren't in the server anymore diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs index 2c44787e5..338c9ce7f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs @@ -70,18 +70,20 @@ namespace Barotrauma { Item droppedItem = item; Entity prevOwner = Owner; + Inventory previousInventory = droppedItem.ParentInventory; droppedItem.Drop(null); + droppedItem.PreviousParentInventory = previousInventory; - var previousInventory = prevOwner switch + var previousCharacterInventory = prevOwner switch { - Item itemInventory => (itemInventory.FindParentInventory(inventory => inventory is CharacterInventory) as CharacterInventory), + Item itemInventory => itemInventory.FindParentInventory(inventory => inventory is CharacterInventory) as CharacterInventory, Character character => character.Inventory, _ => null }; - if (previousInventory != null && previousInventory != c.Character?.Inventory) + if (previousCharacterInventory != null && previousCharacterInventory != c.Character?.Inventory) { - GameMain.Server?.KarmaManager.OnItemTakenFromPlayer(previousInventory, c, droppedItem); + GameMain.Server?.KarmaManager.OnItemTakenFromPlayer(previousCharacterInventory, c, droppedItem); } if (droppedItem.body != null && prevOwner != null) @@ -109,7 +111,8 @@ namespace Barotrauma var holdable = item.GetComponent(); if (holdable != null && !holdable.CanBeDeattached()) { continue; } - if (!prevItems.Contains(item) && !item.CanClientAccess(c)) + if (!prevItems.Contains(item) && !item.CanClientAccess(c) && + (c.Character == null || item.PreviousParentInventory == null || !c.Character.CanAccessInventory(item.PreviousParentInventory))) { #if DEBUG || UNSTABLE DebugConsole.NewMessage($"Client {c.Name} failed to pick up item \"{item}\" (parent inventory: {(item.ParentInventory?.Owner.ToString() ?? "null")}). No access.", Color.Yellow); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index 6e583e89c..ce354ecaf 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -10,6 +10,8 @@ namespace Barotrauma { private CoroutineHandle logPropertyChangeCoroutine; + public Inventory PreviousParentInventory; + public override Sprite Sprite { get { return prefab?.sprite; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs index 2e513b7fa..6c7bf6616 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs @@ -36,21 +36,21 @@ namespace Barotrauma.MapCreatures.Behavior msg.Write(Offset.X); msg.Write(Offset.Y); } - + public void ServerWriteBranchGrowth(IWriteMessage msg, BallastFloraBranch branch, int parentId = -1) { var (x, y) = branch.Position; msg.Write(parentId); msg.Write((int)branch.ID); - msg.WriteRangedInteger((byte) branch.Type, 0b0000, 0b1111); - msg.WriteRangedInteger((byte) branch.Sides, 0b0000, 0b1111); + msg.WriteRangedInteger((byte)branch.Type, 0b0000, 0b1111); + msg.WriteRangedInteger((byte)branch.Sides, 0b0000, 0b1111); msg.WriteRangedInteger(branch.FlowerConfig.Serialize(), 0, 0xFFF); msg.WriteRangedInteger(branch.LeafConfig.Serialize(), 0, 0xFFF); - msg.Write((ushort) branch.MaxHealth); - msg.Write((int) (x / VineTile.Size)); - msg.Write((int) (y / VineTile.Size)); + msg.Write((ushort)branch.MaxHealth); + msg.Write((int)(x / VineTile.Size)); + msg.Write((int)(y / VineTile.Size)); } - + public void ServerWriteBranchDamage(IWriteMessage msg, BallastFloraBranch branch, float damage) { msg.Write((int)branch.ID); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 75273e01c..232ecf417 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -2342,7 +2342,15 @@ namespace Barotrauma.Networking } else { - characterData.SpawnInventoryItems(spawnedCharacter, spawnedCharacter.Inventory); + if (!characterData.HasItemData && !characterData.CharacterInfo.StartItemsGiven) + { + //clients who've chosen to spawn with the respawn penalty can have CharacterData without inventory data + spawnedCharacter.GiveJobItems(mainSubWaypoints[i]); + } + else + { + characterData.SpawnInventoryItems(spawnedCharacter, spawnedCharacter.Inventory); + } characterData.ApplyHealthData(spawnedCharacter); characterData.ApplyOrderData(spawnedCharacter); spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); @@ -3058,13 +3066,7 @@ namespace Barotrauma.Networking case ChatMessageType.Radio: case ChatMessageType.Order: if (senderCharacter == null) { return; } - - //return if senderCharacter doesn't have a working radio - var radio = senderCharacter.Inventory?.AllItems.FirstOrDefault(i => i.GetComponent() != null); - if (radio == null || !senderCharacter.HasEquippedItem(radio)) { return; } - - senderRadio = radio.GetComponent(); - if (!senderRadio.CanTransmit()) { return; } + if (!ChatMessage.CanUseRadio(senderCharacter, out senderRadio)) { return; } break; case ChatMessageType.Dead: //character still alive and capable of speaking -> dead chat not allowed diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index c6cc14e8d..0285f1bc1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -411,11 +411,7 @@ namespace Barotrauma.Networking var characterData = campaign?.GetClientCharacterData(clients[i]); if (characterData != null && Level.Loaded?.Type != LevelData.LevelType.Outpost && characterData.HasSpawned) { - var respawnPenaltyAffliction = AfflictionPrefab.List.FirstOrDefault(a => a.AfflictionType.Equals("respawnpenalty", StringComparison.OrdinalIgnoreCase)); - if (respawnPenaltyAffliction != null) - { - character.CharacterHealth.ApplyAffliction(targetLimb: null, respawnPenaltyAffliction.Instantiate(10.0f)); - } + GiveRespawnPenaltyAffliction(character); } if (characterData == null || characterData.HasSpawned) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Traitors/Goals/GoalFindItem.cs b/Barotrauma/BarotraumaServer/ServerSource/Traitors/Goals/GoalFindItem.cs index f13cb0e1d..582a853fe 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Traitors/Goals/GoalFindItem.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Traitors/Goals/GoalFindItem.cs @@ -78,6 +78,7 @@ namespace Barotrauma List suitableItems = new List(); foreach (Item item in Item.ItemList) { + if (item.HiddenInGame || item.NonInteractable || item.NonPlayerTeamInteractable) { continue; } if (item.Submarine == null || traitors.All(traitor => item.Submarine.TeamID != traitor.Character.TeamID)) { continue; diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index a7f35504a..39607f8d9 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.14.6.0 + 0.1400.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index 4129cb26d..4e3f6b91e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -300,9 +300,9 @@ namespace Barotrauma if (containedItem == null) { continue; } if (predicate == null || predicate(containedItem)) { - if (character.Submarine != Submarine.MainSub && avoidDroppingInSea) + if (avoidDroppingInSea && !character.IsInFriendlySub) { - // If we are outside of main sub, try to put the item in the inventory instead dropping it in the sea. + // If we are not inside a friendly sub (= same team), try to put the item in the inventory instead dropping it. if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.anySlot)) { continue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index e63b0b3bf..169bc5a75 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -2595,7 +2595,7 @@ namespace Barotrauma { if ((SelectedAiTarget != null || wallTarget != null) && IsLatchedOnSub) { - if (!(SelectedAiTarget.Entity is Structure wall)) + if (!(SelectedAiTarget?.Entity is Structure wall)) { wall = wallTarget?.Structure; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index e4d777e7f..b01023b5b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -475,14 +475,16 @@ namespace Barotrauma bool needsGear = NeedsDivingGear(Character.CurrentHull, out _); if (!needsGear || oxygenLow) { - bool shouldKeepTheGearOn = + bool isCurrentObjectiveFindSafety = ObjectiveManager.IsCurrentObjective(); + bool shouldKeepTheGearOn = + isCurrentObjectiveFindSafety || Character.AnimController.InWater || Character.AnimController.HeadInWater || Character.CurrentHull == null || - (Character.Submarine?.TeamID != Character.TeamID && !Character.IsEscorted) || // these instances should maybe be combined to a method - ObjectiveManager.IsCurrentObjective() || + Character.Submarine == null || + (Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) || ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn); - if (oxygenLow && Character.CurrentHull.Oxygen > 0) + if (oxygenLow && Character.CurrentHull.Oxygen > 0 && (!isCurrentObjectiveFindSafety || Character.OxygenAvailable < 1)) { shouldKeepTheGearOn = false; } @@ -1348,13 +1350,15 @@ namespace Barotrauma /// /// Check whether the character has a diving suit in usable condition plus some oxygen. /// - public static bool HasDivingSuit(Character character, float conditionPercentage = 0) => HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true, - predicate: (Item item) => { return character.HasEquippedItem(item, InvSlotType.OuterClothes); }); + public static bool HasDivingSuit(Character character, float conditionPercentage = 0) + => HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true, + predicate: (Item item) => character.HasEquippedItem(item, InvSlotType.OuterClothes)); /// /// Check whether the character has a diving mask in usable condition plus some oxygen. /// - public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true); + public static bool HasDivingMask(Character character, float conditionPercentage = 0) + => HasItem(character, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true); private static List matchingItems = new List(); @@ -2048,7 +2052,7 @@ namespace Barotrauma { var repairItemsObjective = operatingAI.ObjectiveManager.GetObjective(); if (repairItemsObjective == null) { continue; } - if (repairItemsObjective.SubObjectives.None(o => o is AIObjectiveRepairItem repairObjective && repairObjective.Item == target)) + if (!(repairItemsObjective.SubObjectives.FirstOrDefault(o => o is AIObjectiveRepairItem) is AIObjectiveRepairItem activeObjective) || activeObjective.Item != target) { // Not targeting the same item. continue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs index b7d66bb08..5fa7abede 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -133,7 +133,7 @@ namespace Barotrauma } else { - if (ItemToContain.ParentInventory == character.Inventory && character.Submarine == Submarine.MainSub) + if (ItemToContain.ParentInventory == character.Inventory && character.IsInFriendlySub) { ItemToContain.Drop(character); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 942c934d5..e582044fb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -53,7 +53,7 @@ namespace Barotrauma if (HumanAIController.NeedsDivingGear(character.CurrentHull, out bool needsSuit) && (needsSuit ? !HumanAIController.HasDivingSuit(character, conditionPercentage: AIObjectiveFindDivingGear.MIN_OXYGEN) : - !HumanAIController.HasDivingMask(character, conditionPercentage: AIObjectiveFindDivingGear.MIN_OXYGEN))) + !HumanAIController.HasDivingGear(character, conditionPercentage: AIObjectiveFindDivingGear.MIN_OXYGEN))) { Priority = 100; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs index 87fab661f..af2c65a9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -108,6 +108,7 @@ namespace Barotrauma void ReportWeldingFuelTankCount() { + if (character.Submarine != Submarine.MainSub) { return; } int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("weldingfuel") && i.Condition > 1); if (remainingOxygenTanks == 0) { @@ -131,7 +132,7 @@ namespace Barotrauma Abandon = true; return; } - Vector2 toLeak = Leak.WorldPosition - character.WorldPosition; + Vector2 toLeak = Leak.WorldPosition - character.AnimController.AimSourceWorldPos; // TODO: use the collider size/reach? if (!character.AnimController.InWater && Math.Abs(toLeak.X) < 100 && toLeak.Y < 0.0f && toLeak.Y > -150) { @@ -157,6 +158,7 @@ namespace Barotrauma { TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(Leak, character, objectiveManager) { + UseDistanceRelativeToAimSourcePos = true, CloseEnough = reach, DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak" : null, TargetName = Leak.FlowTargetHull?.DisplayName, @@ -165,7 +167,7 @@ namespace Barotrauma onAbandon: () => { if (CheckObjectiveSpecific()) { IsCompleted = true; } - else if ((Leak.WorldPosition - character.WorldPosition).LengthSquared() > MathUtils.Pow(reach * 2, 2)) + else if ((Leak.WorldPosition - character.AnimController.AimSourceWorldPos).LengthSquared() > MathUtils.Pow(reach * 2, 2)) { // Too far Abandon = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index c7ef4fa86..899cb9ba6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -66,6 +66,11 @@ namespace Barotrauma public bool AlwaysUseEuclideanDistance { get; set; } = true; + /// + /// If true, the distance to the destination is calculated from the character's AimSourcePos (= shoulder) instead of the collider's position + /// + public bool UseDistanceRelativeToAimSourcePos { get; set; } = false; + public override bool AbandonWhenCannotCompleteSubjectives => !repeat; public override bool AllowOutsideSubmarine => AllowGoingOutside; @@ -589,7 +594,9 @@ namespace Barotrauma float xDiff = Math.Abs(Target.WorldPosition.X - character.WorldPosition.X); return xDiff <= CloseEnough; } - return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough; + + Vector2 sourcePos = UseDistanceRelativeToAimSourcePos ? character.AnimController.AimSourceWorldPos : character.WorldPosition; + return Vector2.DistanceSquared(Target.WorldPosition, sourcePos) < CloseEnough * CloseEnough; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs index 8e1c022ce..fd42eafd6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -215,7 +215,9 @@ namespace Barotrauma var objective = new AIObjectiveGoTo(Item, character, objectiveManager) { // Don't stop in ladders, because we can't interact with other items while holding the ladders. - endNodeFilter = node => node.Waypoint.Ladders == null + endNodeFilter = node => node.Waypoint.Ladders == null, + // Allow repairing hatches and airlock doors. + AllowGoingOutside = HumanAIController.ObjectiveManager.IsCurrentOrder() && Item.GetComponent() != null }; if (repairTool != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs index f549b42c0..58bc98213 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs @@ -82,6 +82,7 @@ namespace Barotrauma public static bool ViableForRepair(Item item, Character character, HumanAIController humanAIController) { if (!IsValidTarget(item, character)) { return false; } + if (item.CurrentHull == null) { return true; } if (item.CurrentHull.FireSources.Count > 0) { return false; } // Don't repair items in rooms that have enemies inside. if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !humanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; } @@ -150,7 +151,6 @@ namespace Barotrauma if (item.IgnoreByAI(character)) { return false; } if (!item.IsInteractable(character)) { return false; } if (item.IsFullCondition) { return false; } - if (item.CurrentHull == null) { return false; } if (item.Submarine == null || character.Submarine == null) { return false; } //player crew ignores items in outposts if (character.IsOnPlayerTeam && item.Submarine.Info.IsOutpost) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs index 1c7cdc622..4bfa37601 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -234,8 +234,6 @@ namespace Barotrauma new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }), null, 1.0f, "foundwoundedtarget" + targetCharacter.Name, 60.0f); } - - character.SelectCharacter(targetCharacter); } GiveTreatment(deltaTime); } @@ -268,6 +266,8 @@ namespace Barotrauma } treatmentTimer = TreatmentDelay; + float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f; + //find which treatments are the most suitable to treat the character's current condition targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false); @@ -282,6 +282,7 @@ namespace Barotrauma { Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key, true); if (matchingItem == null) { continue; } + character.SelectCharacter(targetCharacter); ApplyTreatment(affliction, matchingItem); //wait a bit longer after applying a treatment to wait for potential side-effects to manifest treatmentTimer = TreatmentDelay * 4; @@ -292,7 +293,6 @@ namespace Barotrauma // Find treatments outside of own inventory only if inside the own sub. if (character.Submarine != null && character.Submarine.TeamID == character.TeamID) { - float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f; //didn't have any suitable treatments available, try to find some medical items if (currentTreatmentSuitabilities.Any(s => s.Value > cprSuitability)) { @@ -329,7 +329,6 @@ namespace Barotrauma new string[2] { targetCharacter.Name, itemListStr }, new bool[2] { false, true }), null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f); } - character.DeselectCharacter(); RemoveSubObjective(ref getItemObjective); TryAddSubObjective(ref getItemObjective, constructor: () => new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC), @@ -345,9 +344,24 @@ namespace Barotrauma } } } + else if (!targetCharacter.IsUnconscious) + { + //no suitable treatments found, not inside our own sub (= can't search for more treatments), the target isn't unconscious (= can't give CPR) + // -> abandon + Abandon = true; + return; + } if (character != targetCharacter) { - character.AnimController.Anim = AnimController.Animation.CPR; + if (cprSuitability > 0.0f) + { + character.SelectCharacter(targetCharacter); + character.AnimController.Anim = AnimController.Animation.CPR; + } + else + { + character.DeselectCharacter(); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs index e7df09aa5..3d5ac5a49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs @@ -63,7 +63,7 @@ namespace Barotrauma allItems = Wreck.GetItems(false); thalamusItems = allItems.FindAll(i => IsThalamus(i.prefab)); hulls.AddRange(Wreck.GetHulls(false)); - var potentialBrainHulls = new Dictionary(); + var potentialBrainHulls = new List<(Hull hull, float weight)>(); brain = new Item(brainPrefab, Vector2.Zero, Wreck); thalamusItems.Add(brain); Point minSize = brain.Rect.Size.Multiply(brain.Scale); @@ -100,10 +100,10 @@ namespace Barotrauma } if (weight > 0) { - potentialBrainHulls.TryAdd(hull, weight); + potentialBrainHulls.Add((hull, weight)); } } - Hull brainHull = ToolBox.SelectWeightedRandom(potentialBrainHulls.Keys.ToList(), potentialBrainHulls.Values.ToList(), Rand.RandSync.Server); + Hull brainHull = ToolBox.SelectWeightedRandom(potentialBrainHulls.Select(pbh => pbh.hull).ToList(), potentialBrainHulls.Select(pbh => pbh.weight).ToList(), Rand.RandSync.Server); var thalamusStructurePrefabs = StructurePrefab.Prefabs.Where(p => IsThalamus(p)); if (brainHull == null) { @@ -187,8 +187,11 @@ namespace Barotrauma if (!spawnOrgans.Contains(item)) { spawnOrgans.Add(item); - // Try to flood the hull so that the spawner won't die. - item.CurrentHull.WaterVolume = item.CurrentHull.Volume; + if (item.CurrentHull != null) + { + // Try to flood the hull so that the spawner won't die. + item.CurrentHull.WaterVolume = item.CurrentHull.Volume; + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index 859b76516..702670425 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs @@ -108,6 +108,16 @@ namespace Barotrauma public enum Animation { None, Climbing, UsingConstruction, Struggle, CPR }; public Animation Anim; + public Vector2 AimSourceWorldPos + { + get + { + Vector2 sourcePos = character.AnimController.AimSourcePos; + if (character.Submarine != null) { sourcePos += character.Submarine.Position; } + return sourcePos; + } + } + public Vector2 AimSourcePos => ConvertUnits.ToDisplayUnits(AimSourceSimPos); public virtual Vector2 AimSourceSimPos => Collider.SimPosition; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 8854ba3d7..7e04b454f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -1463,7 +1463,7 @@ namespace Barotrauma target.CharacterHealth.CalculateVitality(); if (wasCritical && target.Vitality > 0.0f && Timing.TotalTime > lastReviveTime + 10.0f) { - character.Info.IncreaseSkillLevel("medical", SkillSettings.Current.SkillIncreasePerCprRevive, character.Position + Vector2.UnitY * 150.0f); + character.Info?.IncreaseSkillLevel("medical", SkillSettings.Current.SkillIncreasePerCprRevive, character.Position + Vector2.UnitY * 150.0f); SteamAchievementManager.OnCharacterRevived(target, character); lastReviveTime = (float)Timing.TotalTime; #if SERVER diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 1e7a4d476..739f93133 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -576,7 +576,7 @@ namespace Barotrauma get { return pressureProtection; } set { - pressureProtection = Math.Max(value, 0.0f); + pressureProtection = Math.Max(value, pressureProtection); pressureProtectionLastSet = Timing.TotalTime; } } @@ -882,6 +882,8 @@ namespace Barotrauma } } + public bool IsInFriendlySub => Submarine != null && Submarine.TeamID == TeamID; + public delegate void OnDeathHandler(Character character, CauseOfDeath causeOfDeath); public OnDeathHandler OnDeath; @@ -2065,10 +2067,9 @@ namespace Barotrauma if (!CanInteract || inventory.Locked) { return false; } //the inventory belongs to some other character - if (inventory.Owner is Character && inventory.Owner != this) + if (inventory.Owner is Character character && inventory.Owner != this) { - var owner = (Character)inventory.Owner; - + var owner = character; //can only be accessed if the character is incapacitated and has been selected return SelectedCharacter == owner && owner.CanInventoryBeAccessed; } @@ -2361,7 +2362,7 @@ namespace Barotrauma #if CLIENT if (isLocalPlayer) { - if (!IsMouseOnUI) + if (!IsMouseOnUI && (ViewTarget == null || ViewTarget == this)) { if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen) { @@ -2832,7 +2833,7 @@ namespace Barotrauma { if (Timing.TotalTime > pressureProtectionLastSet + 0.1) { - PressureProtection = 0.0f; + pressureProtection = 0.0f; } } if (NeedsWater) @@ -2994,10 +2995,7 @@ namespace Barotrauma { despawnTimer = GameMain.Config.CorpseDespawnDelay; UpdateDespawn(1.0f, ignoreThresholds: true); - if (createNetworkEvents) - { - Spawner.Update(); - } + Spawner.Update(createNetworkEvents); } public static void RemoveByPrefab(CharacterPrefab prefab) @@ -3526,6 +3524,7 @@ namespace Barotrauma } bool wasDead = IsDead; Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir); + float prevVitality = CharacterHealth.Vitality; AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier, penetration: penetration); CharacterHealth.ApplyDamage(hitLimb, attackResult, allowStacking); if (attacker != this) @@ -3534,7 +3533,7 @@ namespace Barotrauma OnAttackedProjSpecific(attacker, attackResult, stun); if (!wasDead) { - TryAdjustAttackerSkill(attacker, -attackResult.Damage); + TryAdjustAttackerSkill(attacker, CharacterHealth.Vitality - prevVitality); if (IsDead) { attacker?.RecordKill(this); @@ -4001,7 +4000,7 @@ namespace Barotrauma } else { - canBePutInOriginalInventory = inventory.CanBePut(newItem, slotIndices[0]); + canBePutInOriginalInventory = inventory.CanBePut(newItem, slotIndices[0], ignoreCondition: true); } if (canBePutInOriginalInventory) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index ab8ec65ef..10907fe96 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -97,7 +97,7 @@ namespace Barotrauma State = InfectionState.Final; ActivateHusk(); ApplyDamage(deltaTime, applyForce: true); - character.SetStun(1); + character.SetStun(5); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index f31a03108..d0a91b108 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -18,10 +18,10 @@ OnFire, InWater, NotInWater, OnImpact, OnEating, - OnDeath = OnBroken, OnDamaged, OnSevered, OnProduceSpawned, OnOpen, OnClose, + OnDeath = OnBroken, } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs index f72877974..f4aa41aa8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs @@ -10,8 +10,8 @@ namespace Barotrauma [Serialize("", true)] public string NPCTag { get; set; } - [Serialize(0, true)] - public int TeamTag { get; set; } + [Serialize(CharacterTeamType.None, true)] + public CharacterTeamType TeamTag { get; set; } [Serialize(false, true)] public bool AddToCrew { get; set; } @@ -29,11 +29,10 @@ namespace Barotrauma affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList(); foreach (var npc in affectedNpcs) { - CharacterTeamType newTeam = (CharacterTeamType)TeamTag; // characters will still remain on friendlyNPC team for rest of the tick - npc.SetOriginalTeam(newTeam); + npc.SetOriginalTeam(TeamTag); - if (AddToCrew && (newTeam == CharacterTeamType.Team1 || newTeam == CharacterTeamType.Team2)) + if (AddToCrew && (TeamTag == CharacterTeamType.Team1 || TeamTag == CharacterTeamType.Team2)) { npc.Info.StartItemsGiven = true; @@ -44,11 +43,11 @@ namespace Barotrauma var wifiComponent = item.GetComponent(); if (wifiComponent != null) { - wifiComponent.TeamID = newTeam; + wifiComponent.TeamID = TeamTag; } } #if SERVER - GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew, newTeam, npc.Inventory.AllItems.Select(it => it.ID).ToArray() }); + GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew, TeamTag, npc.Inventory.AllItems.Select(it => it.ID).ToArray() }); #endif } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 3ede84eeb..3f8a0a93a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -124,13 +124,24 @@ namespace Barotrauma } MTRandom rand = new MTRandom(seed); - var initialEventSet = SelectRandomEvents(EventSet.List, rand); + EventSet initialEventSet = SelectRandomEvents(EventSet.List, rand); + EventSet additiveSet = null; + if (initialEventSet != null && initialEventSet.Additive) + { + additiveSet = initialEventSet; + initialEventSet = SelectRandomEvents(EventSet.List.FindAll(e => !e.Additive), rand); + } if (initialEventSet != null) { pendingEventSets.Add(initialEventSet); CreateEvents(initialEventSet, rand); } - + if (additiveSet != null) + { + pendingEventSets.Add(additiveSet); + CreateEvents(additiveSet, rand); + } + if (level?.LevelData?.Type == LevelData.LevelType.Outpost) { //if the outpost is connected to a locked connection, create an event to unlock it diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs index 7f5ee22a5..54d748b8e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs @@ -94,6 +94,8 @@ namespace Barotrauma public readonly bool TriggerEventCooldown; + public readonly bool Additive; + public readonly Dictionary Commonness; public readonly List<(EventPrefab prefab, float commonness, float probability)> EventPrefabs; @@ -117,6 +119,8 @@ namespace Barotrauma MinLevelDifficulty = element.GetAttributeFloat("minleveldifficulty", 0); MaxLevelDifficulty = Math.Max(element.GetAttributeFloat("maxleveldifficulty", 100), MinLevelDifficulty); + Additive = element.GetAttributeBool("additive", false); + string levelTypeStr = element.GetAttributeString("leveltype", "LocationConnection"); if (!Enum.TryParse(levelTypeStr, true, out LevelType)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index 8364eedca..ee662e36b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -24,6 +24,8 @@ namespace Barotrauma private Submarine sub; + private readonly List previouslySelectedMissions = new List(); + public override string Description { get @@ -42,7 +44,13 @@ namespace Barotrauma { this.sub = sub; itemConfig = prefab.ConfigElement.Element("Items"); - requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.98f), 1.0f); + requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.98f), 1.0f); + //this can get called between rounds when the client receives a campaign save + //don't attempt to determine cargo if the sub hasn't been fully loaded + if (sub == null || sub.Loading || sub.Removed || Submarine.Unloading) + { + return; + } DetermineCargo(); } @@ -58,6 +66,30 @@ namespace Barotrauma List<(ItemContainer container, int freeSlots)> containers = sub.GetCargoContainers(); containers.Sort((c1, c2) => { return c2.container.Capacity.CompareTo(c1.container.Capacity); }); + previouslySelectedMissions.Clear(); + if (GameMain.GameSession?.StartLocation?.SelectedMissions != null) + { + bool isPriorMission = true; + foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions) + { + if (!(mission is CargoMission otherMission)) { continue; } + if (mission == this) { isPriorMission = false; } + previouslySelectedMissions.Add(otherMission); + if (!isPriorMission) { continue; } + foreach (var (element, container) in otherMission.itemsToSpawn) + { + for (int i = 0; i < containers.Count; i++) + { + if (containers[i].container == container) + { + containers[i] = (containers[i].container, containers[i].freeSlots - 1); + break; + } + } + } + } + } + maxItemCount = 0; foreach (XElement subElement in itemConfig.Elements()) { @@ -87,9 +119,9 @@ namespace Barotrauma } calculatedReward = 0; - foreach (var itemToSpawn in itemsToSpawn) + foreach (var (element, container) in itemsToSpawn) { - int price = itemToSpawn.element.GetAttributeInt("reward", Prefab.Reward / itemsToSpawn.Count); + int price = element.GetAttributeInt("reward", Prefab.Reward / itemsToSpawn.Count); if (rewardPerCrate.HasValue) { if (price != rewardPerCrate.Value) { rewardPerCrate = -1; } @@ -108,7 +140,28 @@ namespace Barotrauma public override int GetReward(Submarine sub) { - if (sub != this.sub) + bool missionsChanged = false; + if (GameMain.GameSession?.StartLocation?.SelectedMissions != null) + { + List currentMissions = GameMain.GameSession.StartLocation.SelectedMissions.Where(m => m is CargoMission).ToList(); + if (currentMissions.Count != previouslySelectedMissions.Count) + { + missionsChanged = true; + } + else + { + for (int i = 0; i < previouslySelectedMissions.Count; i++) + { + if (previouslySelectedMissions[i] != currentMissions[i]) + { + missionsChanged = true; + break; + } + } + } + } + + if (sub != this.sub || missionsChanged) { this.sub = sub; DetermineCargo(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 516949ec1..77747d13a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -183,7 +183,7 @@ namespace Barotrauma } } - public virtual void SetDifficulty(float difficulty) { } + public virtual void SetLevel(LevelData level) { } public static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false) { @@ -423,13 +423,16 @@ namespace Barotrauma protected Character CreateHuman(HumanPrefab humanPrefab, List characters, Dictionary> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn = null, Rand.RandSync humanPrefabRandSync = Rand.RandSync.Server, bool giveTags = true) { - if (positionToStayIn == null) - { - positionToStayIn = WayPoint.GetRandom(SpawnType.Human, null, submarine); - } - var characterInfo = humanPrefab.GetCharacterInfo(Rand.RandSync.Server) ?? new CharacterInfo(CharacterPrefab.HumanSpeciesName, npcIdentifier: humanPrefab.Identifier, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync); characterInfo.TeamID = teamType; + + if (positionToStayIn == null) + { + positionToStayIn = + WayPoint.GetRandom(SpawnType.Human, characterInfo.Job?.Prefab, submarine) ?? + WayPoint.GetRandom(SpawnType.Human, null, submarine); + } + Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false); spawnedCharacter.Prefab = humanPrefab; humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs index 0dc3ce997..cb7a4f83a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs @@ -107,7 +107,7 @@ namespace Barotrauma //ruin/cave/wreck items are allowed to spawn close to the sub float minDistance = spawnPositionType == Level.PositionType.Ruin || spawnPositionType == Level.PositionType.Cave || spawnPositionType == Level.PositionType.Wreck ? 0.0f : Level.Loaded.Size.X * 0.3f; - nestPosition = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f); + Level.Loaded.TryGetInterestingPosition(true, spawnPositionType, 0.0f, out Vector2 nestPosition); List spawnEdges = new List(); if (spawnPositionType == Level.PositionType.Cave) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index 57b63b0b7..6d7319de6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -28,6 +28,8 @@ namespace Barotrauma private float pirateSightingUpdateTimer; private Vector2? lastSighting; + private LevelData levelData; + public override int TeamCount => 2; private bool outsideOfSonarRange; @@ -83,21 +85,24 @@ namespace Barotrauma characterTypeConfig = prefab.ConfigElement.Element("CharacterTypes"); addedMissionDifficultyPerPlayer = prefab.ConfigElement.GetAttributeFloat("addedmissiondifficultyperplayer", 0); - // for campaign missions, set difficulty at construction + // for campaign missions, set level at construction LevelData levelData = locations[0].Connections.Where(c => c.Locations.Contains(locations[1])).FirstOrDefault()?.LevelData ?? locations[0]?.LevelData; - - SetDifficulty(levelData?.Difficulty ?? Level.Loaded?.Difficulty ?? 0f); + if (levelData != null) + { + SetLevel(levelData); + } } - public override void SetDifficulty(float difficulty) + public override void SetLevel(LevelData level) { - if (missionDifficulty > 0f) + if (levelData != null) { - // difficulty already set + //level already set return; } - missionDifficulty = difficulty; + levelData = level; + missionDifficulty = level?.Difficulty ?? 0; XElement submarineConfig = GetRandomDifficultyModifiedElement(submarineTypeConfig, missionDifficulty, ShipRandomnessModifier); @@ -123,23 +128,24 @@ namespace Barotrauma submarineInfo = new SubmarineInfo(contentFile.Path); } - private float GetDifficultyModifiedValue(float preferredDifficulty, float levelDifficulty, float randomnessModifier) + private float GetDifficultyModifiedValue(float preferredDifficulty, float levelDifficulty, float randomnessModifier, Random rand) { - return Math.Abs(levelDifficulty - preferredDifficulty + (Rand.Range(-randomnessModifier, randomnessModifier, Rand.RandSync.Server))); + return Math.Abs(levelDifficulty - preferredDifficulty + MathHelper.Lerp(-randomnessModifier, randomnessModifier, (float)rand.NextDouble())); } - private int GetDifficultyModifiedAmount(int minAmount, int maxAmount, float levelDifficulty) + private int GetDifficultyModifiedAmount(int minAmount, int maxAmount, float levelDifficulty, Random rand) { - return Math.Max((int)Math.Round(minAmount + (maxAmount - minAmount) * ((levelDifficulty + Rand.Range(-RandomnessModifier, RandomnessModifier, Rand.RandSync.Server)) / MaxDifficulty)), minAmount); + return Math.Max((int)Math.Round(minAmount + (maxAmount - minAmount) * (levelDifficulty + MathHelper.Lerp(-RandomnessModifier, RandomnessModifier, (float)rand.NextDouble())) / MaxDifficulty), minAmount); } private XElement GetRandomDifficultyModifiedElement(XElement parentElement, float levelDifficulty, float randomnessModifier) { + Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed)); // look for the element that is closest to our difficulty, with some randomness XElement bestElement = null; float bestValue = float.MaxValue; foreach (XElement element in parentElement.Elements()) { - float applicabilityValue = GetDifficultyModifiedValue(element.GetAttributeFloat(0f, "preferreddifficulty"), levelDifficulty, randomnessModifier); + float applicabilityValue = GetDifficultyModifiedValue(element.GetAttributeFloat(0f, "preferreddifficulty"), levelDifficulty, randomnessModifier, rand); if (applicabilityValue < bestValue) { bestElement = element; @@ -154,11 +160,11 @@ namespace Barotrauma Vector2 patrolPos = enemySub.WorldPosition; Point subSize = enemySub.GetDockedBorders().Size; - if (!Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath | Level.PositionType.SidePath, Level.Loaded.Size.X * 0.3f, out preferredSpawnPos)) + if (!Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out preferredSpawnPos)) { DebugConsole.ThrowError("Could not spawn pirate submarine in an interesting location! " + this); } - if (!Level.Loaded.TryGetInterestingPositionAwayFromPoint(true, Level.PositionType.MainPath | Level.PositionType.SidePath, Level.Loaded.Size.X * 0.3f, out patrolPos, preferredSpawnPos, minDistFromPoint: 10000f)) + if (!Level.Loaded.TryGetInterestingPositionAwayFromPoint(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out patrolPos, preferredSpawnPos, minDistFromPoint: 10000f)) { DebugConsole.ThrowError("Could not give pirate submarine an interesting location to patrol to! " + this); } @@ -182,7 +188,7 @@ namespace Barotrauma } } - private void InitPirateShip(Vector2 spawnPos) + private void InitPirateShip() { enemySub.NeutralizeBallast(); if (enemySub.GetItems(alsoFromConnectedSubs: false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent() is Reactor reactor) @@ -193,6 +199,7 @@ namespace Barotrauma enemySub.TeamID = CharacterTeamType.None; //make the enemy sub withstand atleast the same depth as the player sub enemySub.RealWorldCrushDepth = Math.Max(enemySub.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth); + enemySub.ImmuneToBallastFlora = true; } private void InitPirates() @@ -214,12 +221,14 @@ namespace Barotrauma float enemyCreationDifficulty = missionDifficulty + playerCount * addedMissionDifficultyPerPlayer; + Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed)); + bool commanderAssigned = false; foreach (XElement element in characterConfig.Elements()) { // it is possible to get more than the "max" amount of characters if the modified difficulty is high enough; this is intentional // if necessary, another "hard max" value could be used to clamp the value for performance/gameplay concerns - int amountCreated = GetDifficultyModifiedAmount(element.GetAttributeInt("minamount", 0), element.GetAttributeInt("maxamount", 0), enemyCreationDifficulty); + int amountCreated = GetDifficultyModifiedAmount(element.GetAttributeInt("minamount", 0), element.GetAttributeInt("maxamount", 0), enemyCreationDifficulty, rand); for (int i = 0; i < amountCreated; i++) { XElement characterType = characterTypeConfig.Elements().Where(e => e.GetAttributeString("typeidentifier", string.Empty) == element.GetAttributeString("typeidentifier", string.Empty)).FirstOrDefault(); @@ -307,7 +316,7 @@ namespace Barotrauma #endif if (!IsClient) { - InitPirateShip(spawnPos); + InitPirateShip(); } enemySub.SetPosition(spawnPos); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 0ead39f9b..02b62c419 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -651,6 +651,7 @@ namespace Barotrauma location.ClearMissions(); location.Discovered = false; location.LevelData?.EventHistory?.Clear(); + location.UnlockInitialMissions(); } Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation)); Map.SelectLocation(-1); @@ -700,7 +701,7 @@ namespace Barotrauma if (npc == null || interactor == null) { yield return CoroutineStatus.Failure; } HumanAIController humanAI = npc.AIController as HumanAIController; - if (humanAI == null) { yield return CoroutineStatus.Failure; } + if (humanAI == null) { yield return CoroutineStatus.Success; } var waitOrder = Order.PrefabList.Find(o => o.Identifier.Equals("wait", StringComparison.OrdinalIgnoreCase)); humanAI.SetForcedOrder(waitOrder, string.Empty, null); @@ -719,8 +720,10 @@ namespace Barotrauma #if CLIENT ShowCampaignUI = false; #endif - - humanAI.ClearForcedOrder(); + if (!npc.Removed) + { + humanAI.ClearForcedOrder(); + } yield return CoroutineStatus.Success; } @@ -729,13 +732,16 @@ namespace Barotrauma public void AssignNPCMenuInteraction(Character character, InteractionType interactionType) { character.CampaignInteractionType = interactionType; - if (interactionType == InteractionType.None) + character.CharacterHealth.UseHealthWindow = + interactionType == InteractionType.None || + interactionType == InteractionType.Examine || + interactionType == InteractionType.Talk; + + if (interactionType == InteractionType.None) { character.SetCustomInteract(null, null); - return; + return; } - character.CharacterHealth.UseHealthWindow = false; - //character.CanInventoryBeAccessed = false; character.SetCustomInteract( NPCInteract, #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs index 0929c3be8..163255fdd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs @@ -1,4 +1,5 @@ using Barotrauma.Networking; +using System.Globalization; using System.Xml.Linq; namespace Barotrauma @@ -29,20 +30,30 @@ namespace Barotrauma public XElement OrderData { get; private set; } partial void InitProjSpecific(Client client); - public CharacterCampaignData(Client client) + public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false) { Name = client.Name; InitProjSpecific(client); healthData = new XElement("health"); - client.Character.CharacterHealth.Save(healthData); - if (client.Character.Inventory != null) + client.Character?.CharacterHealth?.Save(healthData); + if (giveRespawnPenaltyAffliction) + { + var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction(); + healthData.Add(new XElement("Affliction", + new XAttribute("identifier", respawnPenaltyAffliction.Identifier), + new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture)))); + } + if (client.Character?.Inventory != null) { itemData = new XElement("inventory"); Character.SaveInventory(client.Character.Inventory, itemData); } OrderData = new XElement("orders"); - CharacterInfo.SaveOrderData(client.Character.Info, OrderData); + if (client.Character != null) + { + CharacterInfo.SaveOrderData(client.Character.Info, OrderData); + } } public CharacterCampaignData(XElement element) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 6b1f29e91..b17a7ec74 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -368,8 +368,8 @@ namespace Barotrauma foreach (Mission mission in GameMode.Missions) { - // setting difficulty for missions that may involve difficulty-related submarine creation - mission.SetDifficulty(levelData?.Difficulty ?? 0f); + // setting level for missions that may involve difficulty-related submarine creation + mission.SetLevel(levelData); } if (Submarine.MainSubs[1] == null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs index 9148c6102..1fa008ede 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs @@ -296,10 +296,12 @@ namespace Barotrauma return; } + var linkedItems = GetLinkedItemsToSwap(itemToRemove); + int price = 0; if (!itemToRemove.AvailableSwaps.Contains(itemToInstall)) { - price = itemToInstall.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation); + price = itemToInstall.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count; } if (force) @@ -309,7 +311,7 @@ namespace Barotrauma if (Campaign.Money >= price) { - PurchasedItemSwaps.RemoveAll(p => p.ItemToRemove == itemToRemove); + PurchasedItemSwaps.RemoveAll(p => linkedItems.Contains(p.ItemToRemove)); if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) { // only make the NPC speak if more than 5 minutes have passed since the last purchased service @@ -322,23 +324,27 @@ namespace Barotrauma Campaign.Money -= price; - itemToRemove.AvailableSwaps.Add(itemToRemove.Prefab); - if (itemToInstall != null && !itemToRemove.AvailableSwaps.Contains(itemToInstall)) + foreach (Item itemToSwap in linkedItems) { - itemToRemove.PurchasedNewSwap = true; - itemToRemove.AvailableSwaps.Add(itemToInstall); + itemToSwap.AvailableSwaps.Add(itemToSwap.Prefab); + if (itemToInstall != null && !itemToSwap.AvailableSwaps.Contains(itemToInstall)) + { + itemToSwap.PurchasedNewSwap = true; + itemToSwap.AvailableSwaps.Add(itemToInstall); + } + + if (itemToSwap.Prefab != itemToInstall && itemToInstall != null) + { + itemToSwap.PendingItemSwap = itemToInstall; + PurchasedItemSwaps.Add(new PurchasedItemSwap(itemToSwap, itemToInstall)); + DebugLog($"CLIENT: Swapped item \"{itemToSwap.Name}\" with \"{itemToInstall.Name}\".", Color.Orange); + } + else + { + DebugLog($"CLIENT: Cancelled swapping the item \"{itemToSwap.Name}\" with \"{(itemToSwap.PendingItemSwap?.Name ?? null)}\".", Color.Orange); + } } - if (itemToRemove.Prefab != itemToInstall && itemToInstall != null) - { - itemToRemove.PendingItemSwap = itemToInstall; - PurchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall)); - DebugLog($"CLIENT: Swapped item \"{itemToRemove.Name}\" with \"{itemToInstall.Name}\".", Color.Orange); - } - else - { - DebugLog($"CLIENT: Cancelled swapping the item \"{itemToRemove.Name}\" with \"{(itemToRemove.PendingItemSwap?.Name ?? null)}\".", Color.Orange); - } OnUpgradesChanged?.Invoke(); } else @@ -382,30 +388,55 @@ namespace Barotrauma } } - if (itemToRemove.PendingItemSwap == null) + var linkedItems = GetLinkedItemsToSwap(itemToRemove); + + foreach (Item itemToCancel in linkedItems) { - var replacement = MapEntityPrefab.Find("", swappableItem.ReplacementOnUninstall) as ItemPrefab; - if (replacement == null) + if (itemToCancel.PendingItemSwap == null) { - DebugConsole.ThrowError($"Failed to uninstall item \"{itemToRemove.Name}\". Could not find the replacement item \"{swappableItem.ReplacementOnUninstall}\"."); - return; + var replacement = MapEntityPrefab.Find("", swappableItem.ReplacementOnUninstall) as ItemPrefab; + if (replacement == null) + { + DebugConsole.ThrowError($"Failed to uninstall item \"{itemToCancel.Name}\". Could not find the replacement item \"{swappableItem.ReplacementOnUninstall}\"."); + return; + } + PurchasedItemSwaps.RemoveAll(p => p.ItemToRemove == itemToCancel); + PurchasedItemSwaps.Add(new PurchasedItemSwap(itemToCancel, replacement)); + DebugLog($"Uninstalled item item \"{itemToCancel.Name}\".", Color.Orange); + itemToCancel.PendingItemSwap = replacement; + } + else + { + PurchasedItemSwaps.RemoveAll(p => p.ItemToRemove == itemToCancel); + DebugLog($"Cancelled swapping the item \"{itemToCancel.Name}\" with \"{itemToCancel.PendingItemSwap.Name}\".", Color.Orange); + itemToCancel.PendingItemSwap = null; } - PurchasedItemSwaps.RemoveAll(p => p.ItemToRemove == itemToRemove); - PurchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, replacement)); - DebugLog($"Uninstalled item item \"{itemToRemove.Name}\".", Color.Orange); - itemToRemove.PendingItemSwap = replacement; - } - else - { - PurchasedItemSwaps.RemoveAll(p => p.ItemToRemove == itemToRemove); - DebugLog($"Cancelled swapping the item \"{itemToRemove.Name}\" with \"{itemToRemove.PendingItemSwap.Name}\".", Color.Orange); - itemToRemove.PendingItemSwap = null; } + #if CLIENT OnUpgradesChanged?.Invoke(); #endif } + public List GetLinkedItemsToSwap(Item item) + { + List linkedItems = new List() { item }; + foreach (MapEntity linkedEntity in item.linkedTo) + { + foreach (MapEntity secondLinkedEntity in linkedEntity.linkedTo) + { + if (!(secondLinkedEntity is Item linkedItem) || linkedItem == item) { continue; } + if (linkedItem.AllowSwapping && + linkedItem.Prefab.SwappableItem != null && linkedItem.Prefab.SwappableItem.CanBeBought && + linkedItem.Prefab.SwappableItem.SwapIdentifier.Equals(item.Prefab.SwappableItem.SwapIdentifier, StringComparison.OrdinalIgnoreCase)) + { + linkedItems.Add(linkedItem); + } + } + } + return linkedItems; + } + /// /// Applies all our pending upgrades to the submarine. /// @@ -565,7 +596,7 @@ namespace Barotrauma /// /// /// New level that was applied, -1 if no upgrades were applied. - private static int BuyUpgrade(UpgradePrefab prefab, UpgradeCategory category, Submarine submarine, int level = 1) + private static int BuyUpgrade(UpgradePrefab prefab, UpgradeCategory category, Submarine submarine, int level = 1, Submarine parentSub = null) { int? newLevel = null; if (category.IsWallUpgrade) @@ -604,6 +635,7 @@ namespace Barotrauma foreach (Submarine loadedSub in Submarine.Loaded.Where(sub => sub != submarine)) { + if (loadedSub == parentSub) { continue; } XElement? root = loadedSub.Info?.SubmarineElement; if (root == null) { continue; } @@ -615,7 +647,7 @@ namespace Barotrauma ushort dockingPortID = (ushort) root.GetAttributeInt("originallinkedto", 0); if (dockingPortID > 0 && submarine.GetItems(true).Any(item => item.ID == dockingPortID)) { - BuyUpgrade(prefab, category, loadedSub, level); + BuyUpgrade(prefab, category, loadedSub, level, submarine); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index a72c111f3..f3cbf7c94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -132,17 +132,17 @@ namespace Barotrauma return false; } - public override bool CanBePut(Item item, int i) + public override bool CanBePut(Item item, int i, bool ignoreCondition = false) { return - base.CanBePut(item, i) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) && + base.CanBePut(item, i, ignoreCondition) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) && (SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1); } - public override bool CanBePut(ItemPrefab itemPrefab, int i) + public override bool CanBePut(ItemPrefab itemPrefab, int i, float? condition) { return - base.CanBePut(itemPrefab, i) && + base.CanBePut(itemPrefab, i, condition) && (SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1); } @@ -214,7 +214,7 @@ namespace Barotrauma } } - if (allowedSlots != null && !allowedSlots.Contains(InvSlotType.Any)) + if (allowedSlots != null && allowedSlots.Any() && !allowedSlots.Contains(InvSlotType.Any)) { bool allSlotsTaken = true; foreach (var allowedSlot in allowedSlots) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs index 974921b4d..a5e681c9a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs @@ -167,10 +167,9 @@ namespace Barotrauma.Items.Components { foreach (DockingPort port in list) { - if (port == this || port.item.Submarine == item.Submarine) continue; - - if (Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X) > DistanceTolerance.X) continue; - if (Math.Abs(port.item.WorldPosition.Y - item.WorldPosition.Y) > DistanceTolerance.Y) continue; + if (port == this || port.item.Submarine == item.Submarine || port.IsHorizontal != IsHorizontal) { continue; } + if (Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X) > DistanceTolerance.X) { continue; } + if (Math.Abs(port.item.WorldPosition.Y - item.WorldPosition.Y) > DistanceTolerance.Y) { continue; } return port; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs index 721113743..a86e3f923 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs @@ -82,13 +82,13 @@ namespace Barotrauma.Items.Components get { return nodes; } } - private readonly List> charactersInRange = new List>(); + private readonly List<(Character character, Node node)> charactersInRange = new List<(Character character, Node node)>(); private bool charging; private float timer; - private Attack attack; + private readonly Attack attack; public ElectricalDischarger(Item item, XElement element) : base(item, element) @@ -114,8 +114,8 @@ namespace Barotrauma.Items.Components { //already active, do nothing if (IsActive) { return false; } - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; } + if (character != null && !CharacterUsable) { return false; } CurrPowerConsumption = powerConsumption; charging = true; @@ -182,9 +182,9 @@ namespace Barotrauma.Items.Components FindNodes(item.WorldPosition, Range); if (attack != null) { - foreach (Pair characterInRange in charactersInRange) + foreach ((Character character, Node node) in charactersInRange) { - characterInRange.First.ApplyAttack(null, characterInRange.Second.WorldPosition, attack, 1.0f); + character.ApplyAttack(null, node.WorldPosition, attack, 1.0f); } } DischargeProjSpecific(); @@ -315,7 +315,6 @@ namespace Barotrauma.Items.Components if (closestIndex == -1 || closestDist > currentRange) { - int originalParentNodeIndex = parentNodeIndex; //nothing in range, create some arcs to random directions for (int i = 0; i < Rand.Int(4); i++) { @@ -455,7 +454,7 @@ namespace Barotrauma.Items.Components AddNodesBetweenPoints(currPos, targetPos, 0.25f, ref parentNodeIndex); nodes.Add(new Node(targetPos, parentNodeIndex)); entitiesInRange.RemoveAt(closestIndex); - charactersInRange.Add(new Pair(character, nodes[parentNodeIndex])); + charactersInRange.Add((character, nodes[parentNodeIndex])); FindNodes(entitiesInRange, targetPos, nodes.Count - 1, currentRange); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index 3052da20c..617ef4cc0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs @@ -678,7 +678,7 @@ namespace Barotrauma.Items.Components Reset(); previousGap = leak; } - Vector2 fromCharacterToLeak = leak.WorldPosition - character.WorldPosition; + Vector2 fromCharacterToLeak = leak.WorldPosition - character.AnimController.AimSourceWorldPos; float dist = fromCharacterToLeak.Length(); float reach = AIObjectiveFixLeak.CalculateReach(this, character); @@ -692,10 +692,10 @@ namespace Barotrauma.Items.Components if (!character.AnimController.InWater) { // TODO: use the collider size? - if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController && + if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController humanAnim && Math.Abs(fromCharacterToLeak.X) < 100.0f && fromCharacterToLeak.Y < 0.0f && fromCharacterToLeak.Y > -150.0f) { - ((HumanoidAnimController)character.AnimController).Crouching = true; + humanAnim.Crouching = true; } } if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.inWater)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs index 143226000..9dbf4dd16 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs @@ -12,6 +12,9 @@ namespace Barotrauma.Items.Components private bool midAir; + //continuous collision detection is used while the item is moving faster than this + const float ContinuousCollisionThreshold = 5.0f; + public Character CurrentThrower { get; @@ -61,6 +64,13 @@ namespace Barotrauma.Items.Components if (!item.body.Enabled) { return; } if (midAir) { + if (item.body.FarseerBody.IsBullet) + { + if (item.body.LinearVelocity.LengthSquared() < ContinuousCollisionThreshold * ContinuousCollisionThreshold) + { + item.body.FarseerBody.IsBullet = false; + } + } if (item.body.LinearVelocity.LengthSquared() < 0.01f) { CurrentThrower = null; @@ -156,6 +166,7 @@ namespace Barotrauma.Items.Components //disable platform collisions until the item comes back to rest again item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel; + item.body.FarseerBody.IsBullet = true; midAir = true; ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); @@ -165,6 +176,7 @@ namespace Barotrauma.Items.Components item.body.AngularVelocity = rightHand.body.AngularVelocity; throwPos = 0; throwDone = true; + IsActive = true; if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index 8365d479f..22f334166 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -42,7 +42,7 @@ namespace Barotrauma.Items.Components protected bool canBeCombined; protected bool removeOnCombined; - public bool WasUsed; + public bool WasUsed, WasSecondaryUsed; public readonly Dictionary> statusEffectLists; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 08d3d33ca..1c1a7aafc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Barotrauma.Extensions; +using FarseerPhysics; namespace Barotrauma.Items.Components { @@ -60,7 +61,21 @@ namespace Barotrauma.Items.Components Drawable = !hideItems; } } - + +#if DEBUG + [Editable] +#endif + [Serialize("0.0,0.0", false, description: "The position where the contained items get drawn at (offset from the upper left corner of the sprite in pixels).")] + public Vector2 ItemPos { get; set; } + +#if DEBUG + [Editable] +#endif + [Serialize("0.0,0.0", false, description: "The interval at which the contained items are spaced apart from each other (in pixels).")] + public Vector2 ItemInterval { get; set; } + [Serialize(100, false, description: "How many items are placed in a row before starting a new row.")] + public int ItemsPerRow { get; set; } + [Serialize(true, false, description: "Should the inventory of this item be visible when the item is selected.")] public bool DrawInventory { @@ -118,6 +133,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(false, false, description: "Should the items configured using SpawnWithId spawn if this item is broken.")] + public bool SpawnWithIdWhenBroken + { + get; + set; + } + [Serialize(false, false)] public bool RemoveContainedItemsOnDeconstruct { get; set; } @@ -335,20 +357,66 @@ namespace Barotrauma.Items.Components public void SetContainedItemPositions() { - Vector2 simPos = item.SimPosition; - Vector2 displayPos = item.Position; + Vector2 transformedItemPos = ItemPos * item.Scale; + Vector2 transformedItemInterval = ItemInterval * item.Scale; + Vector2 transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f); + Vector2 transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y); + if (item.body == null) + { + if (item.FlippedX) + { + transformedItemPos.X = -transformedItemPos.X; + transformedItemPos.X += item.Rect.Width; + transformedItemInterval.X = -transformedItemInterval.X; + transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X; + } + if (item.FlippedY) + { + transformedItemPos.Y = -transformedItemPos.Y; + transformedItemPos.Y -= item.Rect.Height; + transformedItemInterval.Y = -transformedItemInterval.Y; + transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y; + } + transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y); + if (Math.Abs(item.Rotation) > 0.01f) + { + Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation)); + transformedItemPos = Vector2.Transform(transformedItemPos, transform); + transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); + transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); + transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform); + } + } + else + { + Matrix transform = Matrix.CreateRotationZ(item.body.Rotation); + if (item.body.Dir == -1.0f) + { + transformedItemPos.X = -transformedItemPos.X; + transformedItemInterval.X = -transformedItemInterval.X; + transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X; + } + transformedItemPos = Vector2.Transform(transformedItemPos, transform); + transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); + transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); + transformedItemPos += item.Position; + } + float currentRotation = itemRotation; if (item.body != null) { currentRotation += item.body.Rotation; } + int i = 0; + Vector2 currentItemPos = transformedItemPos; foreach (Item contained in Inventory.AllItems) { if (contained.body != null) { try { + Vector2 simPos = ConvertUnits.ToSimUnits(currentItemPos); contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, currentRotation); contained.body.SetPrevTransform(contained.body.SimPosition, contained.body.Rotation); contained.body.UpdateDrawPosition(); @@ -365,14 +433,29 @@ namespace Barotrauma.Items.Components contained.Rect = new Rectangle( - (int)(displayPos.X - contained.Rect.Width / 2.0f), - (int)(displayPos.Y + contained.Rect.Height / 2.0f), + (int)(currentItemPos.X - contained.Rect.Width / 2.0f), + (int)(currentItemPos.Y + contained.Rect.Height / 2.0f), contained.Rect.Width, contained.Rect.Height); contained.Submarine = item.Submarine; contained.CurrentHull = item.CurrentHull; - contained.SetContainedItemPositions(); + + i++; + if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f) + { + //interval set on both axes -> use a grid layout + currentItemPos += transformedItemIntervalHorizontal; + if (i % ItemsPerRow == 0) + { + currentItemPos = transformedItemPos; + currentItemPos += transformedItemIntervalVertical * (i / ItemsPerRow); + } + } + else + { + currentItemPos += transformedItemInterval; + } } } @@ -410,7 +493,7 @@ namespace Barotrauma.Items.Components private void SpawnAlwaysContainedItems() { - if (SpawnWithId.Length > 0) + if (SpawnWithId.Length > 0 && (item.Condition > 0.0f || SpawnWithIdWhenBroken)) { string[] splitIds = SpawnWithId.Split(','); foreach (string id in splitIds) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index 173dbc428..c15a5a5b4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -102,6 +102,12 @@ namespace Barotrauma.Items.Components get { return limbPositions.Count > 0; } } + public bool UserInCorrectPosition + { + get; + private set; + } + public bool AllowAiming { get; @@ -149,6 +155,7 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { this.cam = cam; + UserInCorrectPosition = false; if (IsToggle) { @@ -189,6 +196,7 @@ namespace Barotrauma.Items.Components else { user.AnimController.TargetMovement = Vector2.Zero; + UserInCorrectPosition = true; } } else @@ -197,7 +205,7 @@ namespace Barotrauma.Items.Components if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled) { if (Math.Abs(diff.X) > 20.0f) - { + { //wait for the character to walk to the correct position return; } @@ -218,7 +226,8 @@ namespace Barotrauma.Items.Components return; } } - user.AnimController.TargetMovement = Vector2.Zero; + user.AnimController.TargetMovement = Vector2.Zero; + UserInCorrectPosition = true; } } @@ -365,7 +374,7 @@ namespace Barotrauma.Items.Components for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--) { - if (item.LastSentSignalRecipients[i].Item.Condition <= 0.0f) { continue; } + if (item.LastSentSignalRecipients[i].Item.Condition <= 0.0f || item.LastSentSignalRecipients[i].IsPower) { continue; } if (item.LastSentSignalRecipients[i].Item.Prefab.FocusOnSelected) { return item.LastSentSignalRecipients[i].Item; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 2080a95d0..b10901962 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -170,7 +170,7 @@ namespace Barotrauma.Items.Components private void StartFabricating(FabricationRecipe selectedItem, Character user, bool addToServerLog = true) { if (selectedItem == null) { return; } - if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem)) { return; } + if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition)) { return; } #if CLIENT itemList.Enabled = false; @@ -308,7 +308,7 @@ namespace Barotrauma.Items.Components } Character tempUser = user; - int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem); + int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition); for (int i = 0; i < fabricatedItem.Amount; i++) { if (i < amountFittingContainer) @@ -334,7 +334,7 @@ namespace Barotrauma.Items.Components } } - if (user != null && !user.Removed) + if (user?.Info != null && !user.Removed) { foreach (Skill skill in fabricatedItem.RequiredSkills) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs index b46e28726..073ae51cf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs @@ -50,7 +50,7 @@ namespace Barotrauma.Items.Components set { maxFlow = value; } } - [Editable, Serialize(true, false, alwaysUseInstanceValues: true)] + [Editable, Serialize(true, true, alwaysUseInstanceValues: true)] public bool IsOn { get { return IsActive; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index 109645fc6..e8866afaf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs @@ -234,7 +234,10 @@ namespace Barotrauma.Items.Components //use a smoothed "correct output" instead of the actual correct output based on the load //so the player doesn't have to keep adjusting the rate impossibly fast when the load fluctuates heavily - correctTurbineOutput += MathHelper.Clamp((load / MaxPowerOutput * 100.0f) - correctTurbineOutput, -10.0f, 10.0f) * deltaTime; + if (!MathUtils.NearlyEqual(MaxPowerOutput, 0.0f)) + { + correctTurbineOutput += MathHelper.Clamp((load / MaxPowerOutput * 100.0f) - correctTurbineOutput, -10.0f, 10.0f) * deltaTime; + } //calculate tolerances of the meters based on the skills of the user //more skilled characters have larger "sweet spots", making it easier to keep the power output at a suitable level @@ -320,7 +323,7 @@ namespace Barotrauma.Items.Components //reset the fission rate, turbine output and //temperature to optimal levels to prevent fires //at the start of the round - correctTurbineOutput = currentLoad / MaxPowerOutput * 100.0f; + correctTurbineOutput = MathUtils.NearlyEqual(MaxPowerOutput, 0.0f) ? 0.0f : currentLoad / MaxPowerOutput * 100.0f; tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess); optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance); tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index 7e9a9fc27..1d0ad14bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -795,6 +795,7 @@ namespace Barotrauma.Items.Components steeringInput = XMLExtensions.ParseVector2(signal.value, errorMessages: false); steeringInput.X = MathHelper.Clamp(steeringInput.X, -100.0f, 100.0f); steeringInput.Y = MathHelper.Clamp(-steeringInput.Y, -100.0f, 100.0f); + TargetVelocity = steeringInput; } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs index 20cf5e402..f59bc100d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs @@ -74,8 +74,6 @@ namespace Barotrauma.Items.Components [Serialize(100f, true, "How much fertilizer can the planter hold.")] public float FertilizerCapacity { get; set; } - public string LastAction { get; set; } = ""; - public Growable?[] GrowableSeeds = new Growable?[0]; private readonly List SuitableFertilizer = new List(); @@ -119,7 +117,7 @@ namespace Barotrauma.Items.Components GrowableSeeds = new Growable[container.Capacity]; } - public override bool HasRequiredItems(Character character, bool addMessage, string msg = null) + public override bool HasRequiredItems(Character character, bool addMessage, string? msg = null) { if (container?.Inventory == null) { return false; } @@ -137,9 +135,16 @@ namespace Barotrauma.Items.Components return true; } - Msg = MsgHarvest; + if (GrowableSeeds.Any(s => s != null)) + { + Msg = MsgHarvest; + ParseMsg(); + return true; + } + + Msg = string.Empty; ParseMsg(); - return true; + return false; } public override bool Pick(Character character) @@ -163,9 +168,16 @@ namespace Barotrauma.Items.Components switch (plantItem.Type) { case PlantItemType.Seed: - LastAction = "PlantSeed"; ApplyStatusEffects(ActionType.OnPicked, 1.0f, character); - return container.Inventory.TryPutItem(plantItem.Item, character, new List { InvSlotType.Any }); + if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) + { + return container.Inventory.TryPutItem(plantItem.Item, character); + } + else + { + //let the server handle moving the item + return false; + } case PlantItemType.Fertilizer when plantItem.Item != null: float canAdd = FertilizerCapacity - Fertilizer; float maxAvailable = plantItem.Item.Condition; @@ -175,7 +187,6 @@ namespace Barotrauma.Items.Components #if CLIENT character.UpdateHUDProgressBar(this, Item.DrawPosition, Fertilizer / FertilizerCapacity, Color.SaddleBrown, Color.SaddleBrown, "entityname.fertilizer"); #endif - LastAction = "ApplyFertilizer"; ApplyStatusEffects(ActionType.OnPicked, 1.0f, character); return false; } @@ -203,7 +214,6 @@ namespace Barotrauma.Items.Components container?.Inventory.RemoveItem(seed.Item); Entity.Spawner?.AddToRemoveQueue(seed.Item); GrowableSeeds[i] = null; - LastAction = "Harvest"; ApplyStatusEffects(ActionType.OnPicked, 1.0f, character); return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs index f0549f6a9..3c999c05c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs @@ -133,6 +133,12 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { + if (item.Connections == null) + { + IsActive = false; + return; + } + isRunning = true; float chargeRatio = charge / capacity; float gridPower = 0.0f; @@ -173,7 +179,13 @@ namespace Barotrauma.Items.Components } else { - currPowerConsumption = MathHelper.Lerp(currPowerConsumption, rechargeSpeed, 0.05f); + float missingCharge = capacity - charge; + float targetRechargeSpeed = rechargeSpeed; + if (missingCharge < 1.0f) + { + targetRechargeSpeed *= missingCharge; + } + currPowerConsumption = MathHelper.Lerp(currPowerConsumption, targetRechargeSpeed, 0.05f); Charge += currPowerConsumption * Math.Min(Voltage, 1.0f) / 3600.0f; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index 7e5cd03e6..1134861ec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -408,21 +408,25 @@ namespace Barotrauma.Items.Components } } - bool hitSomething = false; + int hitCount = 0; + Vector2 lastHitPos = item.WorldPosition; hits = hits.OrderBy(h => h.Fraction).ToList(); - foreach (HitscanResult h in hits) + for (int i = 0; i < hits.Count; i++) { + var h = hits[i]; item.SetTransform(h.Point, rotation); if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero)) { - LaunchProjSpecific(rayStartWorld, item.WorldPosition); - hitSomething = true; - break; + hitCount++; + if (hitCount >= MaxTargetsToHit || i == hits.Count - 1) + { + LaunchProjSpecific(rayStartWorld, item.WorldPosition); + break; + } } } - - //the raycast didn't hit anything -> the projectile flew somewhere outside the level and is permanently lost - if (!hitSomething) + //the raycast didn't hit anything (or didn't hit enough targets to stop the projectile) -> the projectile flew somewhere outside the level and is permanently lost + if (hitCount < MaxTargetsToHit) { item.body.SetTransformIgnoreContacts(item.body.SimPosition, rotation); LaunchProjSpecific(rayStartWorld, rayEndWorld); @@ -467,7 +471,7 @@ namespace Barotrauma.Items.Components } if (fixture.Body.UserData is VineTile) { return true; } if (fixture.Body.UserData is Item item && (item.GetComponent() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; } - if (fixture.Body.UserData as string == "ruinroom") { return true; } + if (fixture.Body.UserData as string == "ruinroom" || fixture.Body.UserData is Hull || fixture.UserData is Hull) { return true; } //if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub if (submarine != null) @@ -505,7 +509,7 @@ namespace Barotrauma.Items.Components if (fixture.Body.UserData is VineTile) { return -1; } if (fixture.Body.UserData is Item item && (item.GetComponent() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; } - if (fixture.Body?.UserData as string == "ruinroom") { return -1; } + if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; } //ignore everything else than characters, sub walls and level walls if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) && @@ -640,7 +644,7 @@ namespace Barotrauma.Items.Components item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) - dir, item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) + dir, collisionCategory: Physics.CollisionWall); - if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure && + if (wallBody?.FixtureList?.First() != null && (wallBody.UserData is Structure || wallBody.UserData is Item) && //ignore the hit if it's behind the position the item was launched from, and the projectile is travelling in the opposite direction Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0) { @@ -738,7 +742,15 @@ namespace Barotrauma.Items.Components } else if (target.Body.UserData is IDamageable damageable) { - if (Attack != null) { attackResult = Attack.DoDamage(User ?? Attacker, damageable, item.WorldPosition, 1.0f); } + if (Attack != null) + { + Vector2 pos = item.WorldPosition; + if (item.Submarine == null && damageable is Structure structure && structure.Submarine != null && Vector2.DistanceSquared(item.WorldPosition, structure.WorldPosition) > 10000.0f * 10000.0f) + { + item.Submarine = structure.Submarine; + } + attackResult = Attack.DoDamage(User ?? Attacker, damageable, pos, 1.0f); + } } else if (target.Body.UserData is VoronoiCell voronoiCell && voronoiCell.IsDestructible && Attack != null && Math.Abs(Attack.LevelWallDamage) > 0.0f) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 57baf9055..26a50ebf7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -139,7 +139,7 @@ namespace Barotrauma.Items.Components //backwards compatibility var repairThresholdAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("showrepairuithreshold", StringComparison.OrdinalIgnoreCase)) ?? - element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("airepairth44reshold", StringComparison.OrdinalIgnoreCase)); + element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("airepairthreshold", StringComparison.OrdinalIgnoreCase)); if (repairThresholdAttribute != null) { if (float.TryParse(repairThresholdAttribute.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out float repairThreshold)) @@ -349,7 +349,7 @@ namespace Barotrauma.Items.Components foreach (Skill skill in requiredSkills) { float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier); - CurrentFixer.Info.IncreaseSkillLevel(skill.Identifier, + CurrentFixer.Info?.IncreaseSkillLevel(skill.Identifier, SkillSettings.Current.SkillIncreasePerRepair / Math.Max(characterSkillLevel, 1.0f), CurrentFixer.Position + Vector2.UnitY * 100.0f); } @@ -379,7 +379,7 @@ namespace Barotrauma.Items.Components foreach (Skill skill in requiredSkills) { float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier); - CurrentFixer.Info.IncreaseSkillLevel(skill.Identifier, + CurrentFixer.Info?.IncreaseSkillLevel(skill.Identifier, SkillSettings.Current.SkillIncreasePerSabotage / Math.Max(characterSkillLevel, 1.0f), CurrentFixer.Position + Vector2.UnitY * 100.0f); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/DelayComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/DelayComponent.cs index ba755346b..e2f327502 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/DelayComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/DelayComponent.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Xml.Linq; namespace Barotrauma.Items.Components @@ -23,7 +24,7 @@ namespace Barotrauma.Items.Components private int signalQueueSize; private int delayTicks; - private Queue signalQueue; + private readonly Queue signalQueue; private DelayedSignal prevQueuedSignal; @@ -37,7 +38,7 @@ namespace Barotrauma.Items.Components if (value == delay) { return; } delay = value; delayTicks = (int)(delay / Timing.Step); - signalQueueSize = delayTicks * 2; + signalQueueSize = Math.Max(delayTicks, 1) * 2; } } @@ -74,7 +75,14 @@ namespace Barotrauma.Items.Components var signalOut = signalQueue.Peek(); signalOut.SendDuration -= 1; item.SendSignal(new Signal(signalOut.Signal.value, strength: signalOut.Signal.strength), "signal_out"); - if (signalOut.SendDuration <= 0) { signalQueue.Dequeue(); } else { break; } + if (signalOut.SendDuration <= 0) + { + signalQueue.Dequeue(); + } + else + { + break; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Signal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Signal.cs index 6680d82ff..ff178fde8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Signal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Signal.cs @@ -27,7 +27,7 @@ namespace Barotrauma.Items.Components return retVal; } - public static bool operator==(Signal a, Signal b) => + public static bool operator ==(Signal a, Signal b) => a.value == b.value && a.stepsTaken == b.stepsTaken && a.sender == b.sender && @@ -35,6 +35,6 @@ namespace Barotrauma.Items.Components MathUtils.NearlyEqual(a.power, b.power) && MathUtils.NearlyEqual(a.strength, b.strength); - public static bool operator!=(Signal a, Signal b) => !(a == b); + public static bool operator !=(Signal a, Signal b) => !(a == b); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs index fc927d27b..7c6c61021 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs @@ -77,11 +77,10 @@ namespace Barotrauma.Items.Components //item in water -> we definitely want to send the True output isInWater = true; } - else if (item.CurrentHull != null) + else if (item.CurrentHull != null && item.CurrentHull.WaterPercentage > 0.0f) { - //item in not water -> check if there's water anywhere within the rect of the item - if (item.CurrentHull.Surface > item.CurrentHull.Rect.Y - item.CurrentHull.Rect.Height + 1 && - item.CurrentHull.Surface > item.Rect.Y - item.Rect.Height) + //(center of the) item in not water -> check if the water surface is below the bottom of the item's rect + if (item.CurrentHull.Surface > item.Rect.Y - item.Rect.Height) { isInWater = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs index d64c4ea32..200414d89 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs @@ -22,7 +22,9 @@ namespace Barotrauma.Items.Components private string prevSignal; - private int[] channelMemory = new int[ChannelMemorySize]; + private readonly int[] channelMemory = new int[ChannelMemorySize]; + + private Connection signalOutConnection; [Serialize(CharacterTeamType.None, true, description: "WiFi components can only communicate with components that have the same Team ID.", alwaysUseInstanceValues: true)] public CharacterTeamType TeamID { get; set; } @@ -93,6 +95,10 @@ namespace Barotrauma.Items.Components public override void OnItemLoaded() { + if (item.Connections != null) + { + signalOutConnection = item.Connections.Find(c => c.Name == "signal_out"); + } if (channelMemory.All(m => m == 0)) { for (int i = 0; i < channelMemory.Length; i++) @@ -155,6 +161,10 @@ namespace Barotrauma.Items.Components public void TransmitSignal(Signal signal, bool sentFromChat) { + if (sentFromChat) + { + item.LastSentSignalRecipients.Clear(); + } var senderComponent = signal.source?.GetComponent(); if (senderComponent != null && !CanReceive(senderComponent)) { return; } @@ -168,10 +178,14 @@ namespace Barotrauma.Items.Components //signal strength diminishes by distance float sentSignalStrength = signal.strength * MathHelper.Clamp(1.0f - (Vector2.Distance(item.WorldPosition, wifiComp.item.WorldPosition) / wifiComp.range), 0.0f, 1.0f); - Signal s = new Signal(signal.value, signal.stepsTaken, sender: signal.sender, source: signal.source, + Signal s = new Signal(signal.value, ++signal.stepsTaken, sender: signal.sender, source: signal.source, power: 0.0f, strength: sentSignalStrength); - wifiComp.item.SendSignal(s, "signal_out"); - + + if (wifiComp.signalOutConnection != null) + { + wifiComp.item.SendSignal(s, wifiComp.signalOutConnection); + } + if (signal.source != null) { foreach (Connection receiver in wifiComp.item.LastSentSignalRecipients) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs index 01058e3e5..3a054c4e1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs @@ -844,10 +844,11 @@ namespace Barotrauma.Items.Components ClearConnections(); base.RemoveComponentSpecific(); #if CLIENT + if (DraggingWire == this) { draggingWire = null; } overrideSprite?.Remove(); overrideSprite = null; wireSprite = null; #endif - } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 7e93b1ab2..3ea88ae11 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -562,6 +562,7 @@ namespace Barotrauma.Items.Components //use linked projectile containers in case they have to react to the turret being launched somehow //(play a sound, spawn more projectiles) if (!(e is Item linkedItem)) { continue; } + if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; } if (linkedItem.Condition <= 0.0f) { loaderBroken = true; @@ -971,6 +972,7 @@ namespace Barotrauma.Items.Components foreach (MapEntity e in item.linkedTo) { if (!item.IsInteractable(character)) { continue; } + if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; } if (e is Item projectileContainer) { var container = projectileContainer.GetComponent(); @@ -1323,6 +1325,7 @@ namespace Barotrauma.Items.Components CheckProjectileContainer(item, projectiles, out bool _); foreach (MapEntity e in item.linkedTo) { + if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; } if (e is Item projectileContainer) { CheckProjectileContainer(projectileContainer, projectiles, out bool stopSearching); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index 7aa2ccb0f..fb26bafea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -28,22 +28,25 @@ namespace Barotrauma get { return items.Count; } } - public bool CanBePut(Item item) + public bool CanBePut(Item item, bool ignoreCondition = false) { if (item == null) { return false; } if (items.Count > 0) { - if (item.IsFullCondition) + if (!ignoreCondition) { - if (items.Any(it => !it.IsFullCondition)) { return false; } - } - else if (MathUtils.NearlyEqual(item.Condition, 0.0f)) - { - if (items.Any(it => !MathUtils.NearlyEqual(it.Condition, 0.0f))) { return false; } - } - else - { - return false; + if (item.IsFullCondition) + { + if (items.Any(it => !it.IsFullCondition)) { return false; } + } + else if (MathUtils.NearlyEqual(item.Condition, 0.0f)) + { + if (items.Any(it => !MathUtils.NearlyEqual(it.Condition, 0.0f))) { return false; } + } + else + { + return false; + } } if (items[0].Prefab.Identifier != item.Prefab.Identifier || items.Count + 1 > item.Prefab.MaxStackSize) @@ -54,12 +57,31 @@ namespace Barotrauma return true; } - public bool CanBePut(ItemPrefab itemPrefab) + public bool CanBePut(ItemPrefab itemPrefab, float? condition = null) { if (itemPrefab == null) { return false; } if (items.Count > 0) { - if (items.Any(it => !it.IsFullCondition)) { return false; } + if (condition.HasValue) + { + if (MathUtils.NearlyEqual(condition.Value, 0.0f)) + { + if (items.Any(it => it.Condition > 0.0f)) { return false; } + } + else if (MathUtils.NearlyEqual(condition.Value, itemPrefab.Health)) + { + if (items.Any(it => !it.IsFullCondition)) { return false; } + } + else + { + return false; + } + } + else + { + if (items.Any(it => !it.IsFullCondition)) { return false; } + } + if (items[0].Prefab.Identifier != itemPrefab.Identifier || items.Count + 1 > itemPrefab.MaxStackSize) { @@ -70,13 +92,31 @@ namespace Barotrauma } /// Defaults to if null - public int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize = null) + public int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize = null, float? condition = null) { if (itemPrefab == null) { return 0; } maxStackSize ??= itemPrefab.MaxStackSize; if (items.Count > 0) { - if (items.Any(it => !it.IsFullCondition)) { return 0; } + if (condition.HasValue) + { + if (MathUtils.NearlyEqual(condition.Value, 0.0f)) + { + if (items.Any(it => it.Condition > 0.0f)) { return 0; } + } + else if (MathUtils.NearlyEqual(condition.Value, itemPrefab.Health)) + { + if (items.Any(it => !it.IsFullCondition)) { return 0; } + } + else + { + return 0; + } + } + else + { + if (items.Any(it => !it.IsFullCondition)) { return 0; } + } if (items[0].Prefab.Identifier != itemPrefab.Identifier) { return 0; } return maxStackSize.Value - items.Count; } @@ -373,42 +413,42 @@ namespace Barotrauma /// /// Can the item be put in the specified slot. /// - public virtual bool CanBePut(Item item, int i) + public virtual bool CanBePut(Item item, int i, bool ignoreCondition = false) { if (ItemOwnsSelf(item)) { return false; } if (i < 0 || i >= slots.Length) { return false; } - return slots[i].CanBePut(item); + return slots[i].CanBePut(item, ignoreCondition); } - public bool CanBePut(ItemPrefab itemPrefab) + public bool CanBePut(ItemPrefab itemPrefab, float? condition = null) { for (int i = 0; i < capacity; i++) { - if (CanBePut(itemPrefab, i)) { return true; } + if (CanBePut(itemPrefab, i, condition)) { return true; } } return false; } - public virtual bool CanBePut(ItemPrefab itemPrefab, int i) + public virtual bool CanBePut(ItemPrefab itemPrefab, int i, float? condition = null) { if (i < 0 || i >= slots.Length) { return false; } - return slots[i].CanBePut(itemPrefab); + return slots[i].CanBePut(itemPrefab, condition); } - public int HowManyCanBePut(ItemPrefab itemPrefab) + public int HowManyCanBePut(ItemPrefab itemPrefab, float? condition = null) { int count = 0; for (int i = 0; i < capacity; i++) { - count += HowManyCanBePut(itemPrefab, i); + count += HowManyCanBePut(itemPrefab, i, condition); } return count; } - public virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i) + public virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition) { if (i < 0 || i >= slots.Length) { return 0; } - return slots[i].HowManyCanBePut(itemPrefab); + return slots[i].HowManyCanBePut(itemPrefab, condition: condition); } /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index d93664572..0636069b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -149,8 +149,10 @@ namespace Barotrauma set { parentInventory = value; - - if (parentInventory != null) Container = parentInventory.Owner as Item; + if (parentInventory != null) { Container = parentInventory.Owner as Item; } +#if SERVER + PreviousParentInventory = value; +#endif } } @@ -240,7 +242,7 @@ namespace Barotrauma private float rotationRad; - [Editable(0.0f, 360.0f, DecimalCount = 1, ValueStep = 1f), Serialize(0.0f, true)] + [ConditionallyEditable(ConditionallyEditable.ConditionType.AllowRotating, MinValueFloat = 0.0f, MaxValueFloat = 360.0f, DecimalCount = 1, ValueStep = 1f), Serialize(0.0f, true)] public float Rotation { get @@ -722,11 +724,7 @@ namespace Barotrauma public override string ToString() { -#if CLIENT - return (GameMain.DebugDraw) ? Name + " (ID: " + ID + ")" : Name; -#elif SERVER return Name + " (ID: " + ID + ")"; -#endif } private readonly List allPropertyObjects = new List(); @@ -1592,14 +1590,12 @@ namespace Barotrauma { ic.PlaySound(ActionType.Always); ic.UpdateSounds(); - if (!ic.WasUsed) - { - ic.StopSounds(ActionType.OnUse); - ic.StopSounds(ActionType.OnSecondaryUse); - } + if (!ic.WasUsed) { ic.StopSounds(ActionType.OnUse); } + if (!ic.WasSecondaryUsed) { ic.StopSounds(ActionType.OnSecondaryUse); } } #endif ic.WasUsed = false; + ic.WasSecondaryUsed = false; if (ic.IsActive) { @@ -2030,7 +2026,7 @@ namespace Barotrauma //if there's an equal signal waiting to be sent //to the same connection, don't add a new one signal.stepsTaken = 0; - if (!delayedSignals.Contains((signal, connection))) + if (!delayedSignals.Any(s => s.Connection == connection && s.Signal.source == signal.source && s.Signal.value == signal.value && s.Signal.sender == signal.sender)) { delayedSignals.Add((signal, connection)); CoroutineManager.StartCoroutine(DelaySignal(signal, connection)); @@ -2052,8 +2048,11 @@ namespace Barotrauma private IEnumerable DelaySignal(Signal signal, Connection connection) { - //wait one frame - yield return CoroutineStatus.Running; + do + { + //wait at least one frame + yield return CoroutineStatus.Running; + } while (CoroutineManager.DeltaTime <= 0.0f); delayedSignals.Remove((signal, connection)); @@ -2269,7 +2268,7 @@ namespace Barotrauma if (!ic.HasRequiredContainedItems(character, isControlled)) { continue; } if (ic.SecondaryUse(deltaTime, character)) { - ic.WasUsed = true; + ic.WasSecondaryUsed = true; #if CLIENT ic.PlaySound(ActionType.OnSecondaryUse, character); @@ -2773,7 +2772,7 @@ namespace Barotrauma int level = subElement.GetAttributeInt("level", 1); if (upgradePrefab != null) { - item.AddUpgrade(new Upgrade(item, upgradePrefab, level, subElement)); + item.AddUpgrade(new Upgrade(item, upgradePrefab, level, appliedSwap != null ? null : subElement)); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index 83e469f31..1ab7f1687 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -43,27 +43,27 @@ namespace Barotrauma return -1; } - public override bool CanBePut(Item item, int i) + public override bool CanBePut(Item item, int i, bool ignoreCondition = false) { if (ItemOwnsSelf(item)) { return false; } if (i < 0 || i >= slots.Length) { return false; } if (!container.CanBeContained(item)) { return false; } - return item != null && slots[i].CanBePut(item) && slots[i].ItemCount < container.MaxStackSize; + return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.MaxStackSize; } - public override bool CanBePut(ItemPrefab itemPrefab, int i) + public override bool CanBePut(ItemPrefab itemPrefab, int i, float? condition) { if (i < 0 || i >= slots.Length) { return false; } if (!container.CanBeContained(itemPrefab)) { return false; } - return itemPrefab != null && slots[i].CanBePut(itemPrefab) && slots[i].ItemCount < container.MaxStackSize; + return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.MaxStackSize; } - public override int HowManyCanBePut(ItemPrefab itemPrefab, int i) + public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition) { if (itemPrefab == null) { return 0; } if (i < 0 || i >= slots.Length) { return 0; } if (!container.CanBeContained(itemPrefab)) { return 0; } - return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.MaxStackSize)); + return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.MaxStackSize), condition); } public override bool IsFull(bool takeStacksIntoAccount = false) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs index 26d1a03c8..b5adbe740 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs @@ -942,6 +942,7 @@ namespace Barotrauma.MapCreatures.Behavior Anger += 0.01f; + bool wasRemoved = branch.Removed; Branches.Remove(branch); branch.Removed = true; @@ -986,9 +987,11 @@ namespace Barotrauma.MapCreatures.Behavior Kill(); return; } - #if SERVER - SendNetworkMessage(this, NetworkHeader.BranchRemove, branch); + if (!wasRemoved) + { + SendNetworkMessage(this, NetworkHeader.BranchRemove, branch); + } #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index 53c8a8a8f..4e6759d60 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -42,7 +42,7 @@ namespace Barotrauma public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f, float ballastFloraStrength = 0.0f) { - Attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, range) + Attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, Math.Min(range, 1000000)) { SeverLimbsProbability = 1.0f }; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index 068076ada..b662b9c00 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -56,6 +56,7 @@ namespace Barotrauma get { return open; } set { + if (float.IsNaN(value)) { return; } if (value > open) { openedTimer = 1.0f; } open = MathHelper.Clamp(value, 0.0f, 1.0f); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index de7ad710f..ee40e20a9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -525,7 +525,13 @@ namespace Barotrauma public override MapEntity Clone() { - return new Hull(MapEntityPrefab.Find(null, "hull"), rect, Submarine); + var clone = new Hull(MapEntityPrefab.Find(null, "hull"), rect, Submarine); + foreach (KeyValuePair property in SerializableProperties) + { + if (!property.Value.Attributes.OfType().Any()) { continue; } + clone.SerializableProperties[property.Key].TrySetValue(clone, property.Value.GetValue(this)); + } + return clone; } public static EntityGrid GenerateEntityGrid(Rectangle worldRect) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 382c0a8ab..e104b4ccc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -1837,7 +1837,7 @@ namespace Barotrauma foreach (RuinShape ruinShape in ruin.RuinShapes) { - var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height)); + var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height) * 4); foreach (VoronoiCell cell in tooClose) { @@ -1984,7 +1984,7 @@ namespace Barotrauma private DestructibleLevelWall CreateIceSpire(List usedSpireEdges) { const float maxLength = 15000.0f; - + float minEdgeLength = 100.0f; var mainPathPos = PositionsOfInterest.Where(pos => pos.PositionType == PositionType.MainPath).GetRandom(Rand.RandSync.Server); double closestDistSqr = double.PositiveInfinity; GraphEdge closestEdge = null; @@ -1999,8 +1999,9 @@ namespace Barotrauma if (edge.Center.Y > Size.Y / 2 && (edge.Center.X < Size.X * 0.3f || edge.Center.X > Size.X * 0.7f)) { continue; } if (Vector2.DistanceSquared(edge.Center, StartPosition) < maxLength * maxLength) { continue; } if (Vector2.DistanceSquared(edge.Center, EndPosition) < maxLength * maxLength) { continue; } - //don't spawn on very long edges - if (Vector2.DistanceSquared(edge.Point1, edge.Point2) > 1000.0f * 1000.0f) { continue; } + //don't spawn on very long or very short edges + float edgeLengthSqr = Vector2.DistanceSquared(edge.Point1, edge.Point2); + if (edgeLengthSqr > 1000.0f * 1000.0f || edgeLengthSqr < minEdgeLength * minEdgeLength) { continue; } //don't spawn on edges facing away from the main path if (Vector2.Dot(Vector2.Normalize(mainPathPos.Position.ToVector2()) - edge.Center, edge.GetNormal(cell)) < 0.5f) { continue; } double distSqr = MathUtils.DistanceSquared(edge.Center.X, edge.Center.Y, mainPathPos.Position.X, mainPathPos.Position.Y); @@ -3048,8 +3049,10 @@ namespace Barotrauma var waypoints = WayPoint.WayPointList.Where(wp => wp.Submarine == null && wp.SpawnType == SpawnType.Path && + wp.WorldPosition.X < EndExitPosition.X && !IsCloseToStart(wp.WorldPosition, minDistance) && - !IsCloseToEnd(wp.WorldPosition, minDistance)).ToList(); + !IsCloseToEnd(wp.WorldPosition, minDistance) + ).ToList(); var subDoc = SubmarineInfo.OpenFile(contentFile.Path); Rectangle subBorders = Submarine.GetBorders(subDoc.Root); @@ -3134,7 +3137,7 @@ namespace Barotrauma } tempSW.Stop(); Debug.WriteLine($"Sub {sub.Info.Name} loaded in { tempSW.ElapsedMilliseconds} (ms)"); - sub.SetPosition(spawnPoint); + sub.SetPosition(spawnPoint, forceUndockFromStaticSubmarines: false); wreckPositions.Add(sub, positions); blockedRects.Add(sub, rects); return sub; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelWall.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelWall.cs index f4d08b8a0..186c6bf20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelWall.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelWall.cs @@ -75,6 +75,10 @@ namespace Barotrauma } Cells = new List() { wallCell }; Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles); + if (triangles.Count == 0) + { + throw new ArgumentException("Failed to generate a wall (not enough triangles). Original vertices: " + string.Join(", ", originalVertices.Select(v => v.ToString()))); + } #if CLIENT GenerateVertices(); #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index d8aa3610e..153bfa954 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -192,6 +192,7 @@ namespace Barotrauma if (!SelectedMissions.Contains(mission) && mission != null) { selectedMissions.Add(mission); + selectedMissions.Sort((m1, m2) => availableMissions.IndexOf(m1).CompareTo(availableMissions.IndexOf(m2))); } } @@ -461,6 +462,18 @@ namespace Barotrauma CreateStore(force: true); } + public void UnlockInitialMissions() + { + if (Type.MissionIdentifiers.Any()) + { + UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(Rand.RandSync.Server)); + } + if (Type.MissionTags.Any()) + { + UnlockMissionByTag(Type.MissionTags.GetRandom(Rand.RandSync.Server)); + } + } + public void UnlockMission(MissionPrefab missionPrefab, LocationConnection connection) { if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index 77cd99589..e49683e88 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -484,14 +484,7 @@ namespace Barotrauma { Difficulty = MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f) }; - if (location.Type.MissionIdentifiers.Any()) - { - location.UnlockMissionByIdentifier(location.Type.MissionIdentifiers.GetRandom()); - } - if (location.Type.MissionTags.Any()) - { - location.UnlockMissionByTag(location.Type.MissionTags.GetRandom()); - } + location.UnlockInitialMissions(); } foreach (LocationConnection connection in Connections) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index fd433482a..23644df10 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -1078,7 +1078,7 @@ namespace Barotrauma #endif } - float gapOpen = (damage / MaxHealth - LeakThreshold) * (1.0f / (1.0f - LeakThreshold)); + float gapOpen = MaxHealth <= 0.0f ? 0.0f : (damage / MaxHealth - LeakThreshold) * (1.0f / (1.0f - LeakThreshold)); Sections[sectionIndex].gap.Open = gapOpen; } @@ -1095,7 +1095,7 @@ namespace Barotrauma { if (damageDiff < 0.0f) { - attacker.Info.IncreaseSkillLevel("mechanical", + attacker.Info?.IncreaseSkillLevel("mechanical", -damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f), SectionPosition(sectionIndex)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 143f60816..645e8e28c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -282,9 +282,11 @@ namespace Barotrauma } private float ballastFloraTimer; + public bool ImmuneToBallastFlora { get; set; } public void AttemptBallastFloraInfection(string identifier, float deltaTime, float probability) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + if (ImmuneToBallastFlora) { return; } if (ballastFloraTimer < 1f) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index 4c94d8128..5fe93ad3b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -671,17 +671,20 @@ namespace Barotrauma Body.LinearVelocity -= velChange; - float damageAmount = contactDot * Body.Mass / limb.character.Mass; - limb.character.LastDamageSource = submarine; - limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb, - AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), 0.0f, true, 0.0f); - - if (limb.character.IsDead) + if (contactDot > 0.1f) { - foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints) + float damageAmount = contactDot * Body.Mass / limb.character.Mass; + limb.character.LastDamageSource = submarine; + limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb, + AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), 0.0f, true, 0.0f); + + if (limb.character.IsDead) { - if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) continue; - limb.character.AnimController.SeverLimbJoint(limbJoint); + foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints) + { + if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) continue; + limb.character.AnimController.SeverLimbJoint(limbJoint); + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs index 731c4e0f0..8e718636a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs @@ -404,6 +404,8 @@ namespace Barotrauma { var vanillaSubs = vanilla.GetFilesOfType(ContentType.Submarine) .Concat(vanilla.GetFilesOfType(ContentType.Wreck)) + .Concat(vanilla.GetFilesOfType(ContentType.BeaconStation)) + .Concat(vanilla.GetFilesOfType(ContentType.EnemySubmarine)) .Concat(vanilla.GetFilesOfType(ContentType.Outpost)) .Concat(vanilla.GetFilesOfType(ContentType.OutpostModule)); string pathToCompare = FilePath.Replace(@"\", @"/").ToLowerInvariant(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs index 65d32afa7..2f7152ffa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs @@ -779,6 +779,7 @@ namespace Barotrauma public static WayPoint[] SelectCrewSpawnPoints(List crew, Submarine submarine) { List subWayPoints = WayPointList.FindAll(wp => wp.Submarine == submarine); + subWayPoints.Shuffle(); List unassignedWayPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human); @@ -789,7 +790,7 @@ namespace Barotrauma //try to give the crew member a spawnpoint that hasn't been assigned to anyone and matches their job for (int n = 0; n < unassignedWayPoints.Count; n++) { - if (crew[i].Job.Prefab != unassignedWayPoints[n].AssignedJob) continue; + if (crew[i].Job.Prefab != unassignedWayPoints[n].AssignedJob) { continue; } assignedWayPoints[i] = unassignedWayPoints[n]; unassignedWayPoints.RemoveAt(n); @@ -800,17 +801,17 @@ namespace Barotrauma //go through the crewmembers that don't have a spawnpoint yet (if any) for (int i = 0; i < crew.Count; i++) { - if (assignedWayPoints[i] != null) continue; + if (assignedWayPoints[i] != null) { continue; } //try to assign a spawnpoint that matches the job, even if the spawnpoint is already assigned to someone else foreach (WayPoint wp in subWayPoints) { - if (wp.spawnType != SpawnType.Human || wp.AssignedJob != crew[i].Job.Prefab) continue; + if (wp.spawnType != SpawnType.Human || wp.AssignedJob != crew[i].Job.Prefab) { continue; } assignedWayPoints[i] = wp; break; } - if (assignedWayPoints[i] != null) continue; + if (assignedWayPoints[i] != null) { continue; } //try to assign a spawnpoint that isn't meant for any specific job var nonJobSpecificPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human && wp.AssignedJob == null); @@ -819,7 +820,7 @@ namespace Barotrauma assignedWayPoints[i] = nonJobSpecificPoints[Rand.Int(nonJobSpecificPoints.Count, Rand.RandSync.Server)]; } - if (assignedWayPoints[i] != null) continue; + if (assignedWayPoints[i] != null) { continue; } //everything else failed -> just give a random spawnpoint inside the sub assignedWayPoints[i] = GetRandom(SpawnType.Human, null, submarine, useSyncedRand: true); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs index c60de64dd..f6cfdd76e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs @@ -264,9 +264,17 @@ namespace Barotrauma.Networking { radio = null; if (sender?.Inventory == null || sender.Removed) { return false; } - radio = sender.Inventory.AllItems.FirstOrDefault(i => i.GetComponent() != null)?.GetComponent(); - if (radio?.Item == null) { return false; } - return sender.HasEquippedItem(radio.Item) && radio.CanTransmit(); + + foreach (Item item in sender.Inventory.AllItems) + { + var wifiComponent = item.GetComponent(); + if (wifiComponent == null || !wifiComponent.CanTransmit() || !sender.HasEquippedItem(item)) { continue; } + if (radio == null || wifiComponent.Range > radio.Range) + { + radio = wifiComponent; + } + } + return radio?.Item != null; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs index a899a5569..3d6b8ea44 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs @@ -377,7 +377,7 @@ namespace Barotrauma return removeQueue.Contains(entity); } - public void Update() + public void Update(bool createNetworkEvents = true) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } while (spawnQueue.Count > 0) @@ -387,10 +387,13 @@ namespace Barotrauma var spawnedEntity = entitySpawnInfo.Spawn(); if (spawnedEntity != null) { - CreateNetworkEventProjSpecific(spawnedEntity, false); - if (spawnedEntity is Item) + if (createNetworkEvents) + { + CreateNetworkEventProjSpecific(spawnedEntity, false); + } + if (spawnedEntity is Item item) { - ((Item)spawnedEntity).Condition = ((ItemSpawnInfo)entitySpawnInfo).Condition; + item.Condition = ((ItemSpawnInfo)entitySpawnInfo).Condition; } entitySpawnInfo.OnSpawned(spawnedEntity); } @@ -403,7 +406,10 @@ namespace Barotrauma { item.SendPendingNetworkUpdates(); } - CreateNetworkEventProjSpecific(removedEntity, true); + if (createNetworkEvents) + { + CreateNetworkEventProjSpecific(removedEntity, true); + } removedEntity.Remove(); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs index a82fc6111..c0074d353 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs @@ -305,7 +305,22 @@ namespace Barotrauma.Networking { RespawnCharactersProjSpecific(shuttlePos); } - + + public static Affliction GetRespawnPenaltyAffliction() + { + var respawnPenaltyAffliction = AfflictionPrefab.List.FirstOrDefault(a => a.AfflictionType.Equals("respawnpenalty", StringComparison.OrdinalIgnoreCase)); + return respawnPenaltyAffliction?.Instantiate(10.0f); + } + + public static void GiveRespawnPenaltyAffliction(Character character) + { + var respawnPenaltyAffliction = GetRespawnPenaltyAffliction(); + if (respawnPenaltyAffliction != null) + { + character.CharacterHealth.ApplyAffliction(targetLimb: null, respawnPenaltyAffliction); + } + } + public Vector2 FindSpawnPos() { if (Level.Loaded == null || Submarine.MainSub == null) { return Vector2.Zero; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index f04d1fc1a..5002ddf18 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -76,7 +76,8 @@ namespace Barotrauma //These need to exist at compile time, so it is a little awkward //I would love to see a better way to do this AllowLinkingWifiToChat, - IsSwappableItem + IsSwappableItem, + AllowRotating } public bool IsEditable(ISerializableEntity entity) @@ -86,7 +87,13 @@ namespace Barotrauma case ConditionType.AllowLinkingWifiToChat: return GameMain.NetworkMember?.ServerSettings?.AllowLinkingWifiToChat ?? true; case ConditionType.IsSwappableItem: - return entity is Item item && item.Prefab.SwappableItem != null; + { + return entity is Item item && item.Prefab.SwappableItem != null; + } + case ConditionType.AllowRotating: + { + return entity is Item item && item.Prefab.AllowRotatingInEditor && Screen.Selected == GameMain.SubEditorScreen; + } } return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 01bb1451e..41c47a0b7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -685,7 +685,7 @@ namespace Barotrauma if (HasTargetType(TargetType.NearbyItems)) { //optimization for powered components that can be easily fetched from Powered.PoweredList - if (targetIdentifiers.Count == 1 && + if (targetIdentifiers?.Count == 1 && (targetIdentifiers.Contains("powered") || targetIdentifiers.Contains("junctionbox") || targetIdentifiers.Contains("relaycomponent"))) { foreach (Powered powered in Powered.PoweredList) @@ -971,6 +971,13 @@ namespace Barotrauma { position = targetLimb.WorldPosition; } + else if (HasTargetType(TargetType.Contained)) + { + if (targets.FirstOrDefault(t => t is Item) is Item targetItem) + { + position = targetItem.WorldPosition; + } + } } } position += Offset; diff --git a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub index 8cd68e513..73038ebb9 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub and b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub index 3e0c87dbc..773c3ee81 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub and b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub b/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub index 9f6a1c043..c595276b0 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub and b/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 642369fbc..879c0d4f2 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub index a3aba369b..c7ca33522 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub and b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub b/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub index 648425a0b..0b77952ca 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca.sub b/Barotrauma/BarotraumaShared/Submarines/Orca.sub index 10ea9b77e..6b3646cab 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/R-29.sub b/Barotrauma/BarotraumaShared/Submarines/R-29.sub index a9c57b323..97053ee07 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/R-29.sub and b/Barotrauma/BarotraumaShared/Submarines/R-29.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index a58fee96f..3b45491dc 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub index 6b9f2045f..e75e187ce 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub index 6eaf45b30..efb5e3678 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Venture.sub b/Barotrauma/BarotraumaShared/Submarines/Venture.sub index 0ce3c7daa..a5602a06c 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Venture.sub and b/Barotrauma/BarotraumaShared/Submarines/Venture.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 2bfab309f..62882e00e 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,109 @@ +--------------------------------------------------------------------------------------------------------- +v0.14.7.0 +--------------------------------------------------------------------------------------------------------- + +Changes: +- If multiple turrets have been linked to the same loader, the turrets can't be swapped individually (both need to be swapped at the same time to prevent turrets from getting linked to an incorrect type of loader). +- Emergency lights don't cast shadows by default (they have such a short range that disabling the shadows usually doesn't make a difference visually, but improves the performance of the subs). +- Allow closing the submarine previews with esc. +- Made ballast pumps in Kastrull's drone indestructible (because they're impossible to access without breaking the walls/floors). +- Made exploding coilgun ammo more expensive and increased the capacity of the ammo boxes a bit. +- Made pirate subs immune to ballast flora. +- Security's stun guns spawn fully loaded. +- Reduced escort missions' base commonness. Doesn't have an effect in the campaign because the commonness is defined in the mission events, but fixes escort missions being much more common than other mission types in mission mode. +- Destroying a reactor with explosives depletes the fuel rods. + +Fixes: +- Fixed enemies sometimes not spawning at all during a mission. Happened when the game selected the "stowaway" event to occur during the mission. +- Fixed pirate and escort missions not being available from late-game outposts. +- Fixed occasional "unauthorized multithreaded access to RandSync.Server" errors when starting a pirate mission in multiplayer. +- Fixed crashing with a "E_INVALIDARG" SharpDXException if an ice spire happens to generate on a very short level wall edge. +- Fixed batteries always recharging at "full speed" when not full, regardless of how much power is being drawn from the battery. E.g. a battery that's only connected to a lamp drawing 5 kW of power would always recharge at the full 500 kWmin. +- Fixed traitor items sometimes spawning in non-interactable or hidden containers. +- Fixed turret rotation limit widgets working unreliably in the sub editor. +- Fixed barrels being misaligned on scaled turrets in the sub preview. +- Stores don't offer/request sealed supply crates as daily specials. +- Fixed campaign settings (radiation enabled, max missions) resetting when reopening the campaign settings menu. +- Fixed hitscan projectiles not hitting structures outside hulls when the turret itself is inside a hull. +- Fixed inability to swap items between the character's inventory and a container in some situations in multiplayer (when the container was right at the edge of the character's interaction range). +- Fixed bots being unable to repair the pump in Typhon's bottom airlock. +- Fixed crashing when repairing or fabricating something as a husk (or some other character with no CharacterInfo). +- Fixed crashing when reviving someone using CPR as a character with no CharacterInfo (e.g. husk). +- Fixed upgrades using the original value from a previous item when swapping to a new one (e.g. swapping a railgun to a coilgun sets the power consumption to the railgun's power consumption). More specifically, this happened when the campaign was saved and reloaded after purchasing the upgrade, and the item was swapped after that. +- Fixed wrecks sometimes being positioned right side of the exit position. +- Fixed characters not getting knocked down at the last stage of husk infection if they have the Vigor buff. +- Fixed discharge coil triggering when trying to rewire it. +- Fixed some ActionTypes having the same value (e.g. OnFire == OnDamaged). Caused wrong type of status effects triggering in some cases, for example OnDamage effects when an indestructible thalamus organ is on fire. +- Fixed items that are set to be hidden in-game being visible in the sub preview. +- Fixed bots being unable to weld leaks that are too high above them, even if they're actually in reach. Happened because the distance to the leak was calculated relative to the character's position, even though the range of the welding tool is relative to the character's shoulder. +- Fixed cargo mission rewards being displayed incorrectly when there's multiple cargo missions selected and not enough capacity for all the cargo. +- Fixed caret getting misplaced when clicking on a textbox with padding. +- Fixed headset only being able to send signals to each wifi component once. +- Fixed bots sometimes ignoring broken devices. Happened when another bot had selected the device as their repair target, even if the bot was currently repairing something else. +- Fixed nav terminal labels sometimes being draw under linked status monitor HUDs. +- Fixed bots sometimes not fixing airlock doors/hatches. More specifically, doors whose center point was outside a hull. +- Fixed medic bots grabbing the target and never letting go when there's no suitable treatments available anywhere in the sub, or when they're in an outpost and not carrying any suitable treatments. +- Fixed ability to select other items when operating periscopes. Didn't cause problems in vanilla subs, but in custom subs where the turret was placed close to the periscope and other interactable items, it was possible to accidentally select something else when trying to fire. +- Made nav terminal's "velocity_in" input change the target velocity, not just the steering input, making it possible to adjust the velocity with signals when using autopilot. +- Fixed text fields in a component's editing menu not refreshing until you've interacted with another component. +- Fixed ruins sometimes overlapping with level walls (when they happen to spawn next to a very large cell). +- Fixed ability to "partially open" the health interface in the sub editor, causing the inventory layout to get messed up when clicking on the character portrait even though the health interface isn't drawn. +- Fixed ability to keep dragging an item from a stunned/ragdolled character's inventory after they get back up. +- Fixed nuclear weapons not damaging ballast flora. +- Fixed normal uniforms deflecting projectiles even though they shouldn't. +- Fixed clients who've opted to spawn with reaper's tax getting a new character if the round ends before the client respawns. +- Fixed occasional crash with the error message "collection was modified; enumeration operation may not execute" when loading mods on startup. +- Fixed ability to keep dragging the previously equipped wire in the sub editor after you equip another wire with the wire hotkeys. +- Fixed characters' sprite depth changing during the "autowalk" towards a bed or chair. +- Require the sub to move a bit faster to crush characters between the sub and a wall. Previously essentially any non-zero velocity was enough to crush a character, even if the sub didn't appear to move. +- Fixed nests sometimes getting placed outside caves. Happened when the game tried to position the nest above an opening at the bottom of the cave. +- Fixed inability to use the health interface on the escorted characters. +- Fixed 1st client (usually the host) always spawning at the same spawnpoint in multiplayer even if there's multiple suitable ones available. +- Fixed stacked partially used items (e.g. stacked explosives that have been damaged) dropping from character inventories at the start of a round. +- Fixed occasional freezing when replacing lost shuttles after purchasing submarine upgrades. +- Adjusted Endworm's colliders so that you can't hit the tail between the armor segments before first breaking the armor (making it less easy to cut the worm in half). +- Fixed hull properties not carrying over when copying hulls in the sub editor. +- Fixed occasional "collection was modified" exception in CargoMission.DetermineCargo. Happened if the client received an updated campaign save while trying to load the sub between rounds. +- Fixed a broken waypoint in Berilia's cargo bay. +- Fixed seeds sometimes vanishing when trying to plant them in MP. +- Fixed planter boxes displaying the "uproot" message when empty. +- Fixed depth charges going through doors and hatches. +- Fixed ability to dock docking hatches to ports and vice versa. +- Fixed camera being able to focus on a turret when a periscope's "position_out" connection is wired to a turret's "power_in" connection. +- Fixed water detectors not detecting very small (water depth < 1px) amounts of water in hulls. +- Fixed duffel bags not spawning at the end of a round if the character hasn't despawned/respawned yet (i.e. if the character's corpse is still present in the sub). +- Fixed wrecked Dugong's distress signal being impossible to receive due to "allow cross-team communication" being set to false on the wifi component sending the signal. +- Fixed fabricator being unable to stack empty items in the output slots, preventing empty tanks from being fabricated when the output slots are occupied even if additional tanks could be stacked on them. +- Fixed messed up "Shuttle Shell A Glass A" sprite. +- Fixed delay components not working if the delay is set to 0. +- Fixed loaders getting slightly misaligned when swapping an empty hardpoint with some weapon. +- Fixed pumps placed in the sub editor being off by default. +- Fixed weapon skill increases not being capped according to the max vitality of the target. Resulted in enormous weapon skill gains when doing massive amounts of damage (more than the target's max vitality), e.g. by shooting a hammerhead matriarch in the egg sack with a nuke. +- Fixed ability to delete vanilla beacon stations and pirate subs in the sub editor. +- Fixed pirate subs sometimes spawning in side paths that are too narrow for the sub to pass through. +- Fixed an exploit that allowed creating game-crashing infinite signal loops using wifi components. +- Fixed a typo in the abyss diving suit description (100,000 m -> 10,000 m). +- Fixed several vanilla sub docking hatches requiring a welding tool instead of a wrench to repair them. +- Fixed "Operate Weapons" order being available when interacting with a turret directly, leading to a crash when trying to do so. +- Fixed missions disappearing from abandoned outposts after finishing the campaign. +- Fixed canister shells refilling automatically between rounds. +- Fixed turrets working with incorrect loader types (e.g. coilgun being able to fire laser bolts when linked to a pulse laser loader). +- Fixed "exterior pressure exceeds diving suit capabilities" hint popping up when carrying a diving suit. + +Modding: +- Added support for "additive" event sets which get added on top of another normal event set, allowing mods to spawn additional types of monsters without having to touch the vanilla event sets. See the "transitevents" event set in OutpostEvents.xml for an usage example. +- Fixed subs appearing to disappear after being published in the Workshop, because the content package the sub gets moved to wasn't automatically selected. +- Fixed MaxTargetsToHit not working on hitscan projectiles. +- Fixed offsets not being taken into account when positioning contained items, causing status effects to happen at the position of the container instead of the position of the contained item. Didn't have a noticeable effect in the vanilla game because most contained items are positioned close to the container's origin. +- Fixed spawnpoint's job restrictions being ignored when spawning pirates and escortees. +- It's now possible to use multiple equipped WifiComponents at the same time: instead of finding the first equipped item and seeing if it can receive/transmit, the game now goes through the items until it finds one that can. +- Fixed crashing when trying to pick an item that can't be put in any type of inventory slot from a container. Doesn't happen in the vanilla game because there are no such items in any container. +- Fixed crashing during wreck generation if there's any thalamus organs outside hulls. +- Fixed crashing if a wall whose max health is set to 0 in the sub editor takes damage. +- Fixed crashing if a reactor's maximum output is set to 0 and it's set to be on by default. +- Fixed a crashing due to a null reference exception if a status effect uses "target" instead of "targets" or "targettypes" in the definition. +- Fixed shuttles docked to a wreck undocking during level generation. + --------------------------------------------------------------------------------------------------------- v0.14.6.0 ---------------------------------------------------------------------------------------------------------