diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs index cdf6ce5f5..51bf3a451 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs @@ -48,8 +48,13 @@ namespace Barotrauma { color = Color.CornflowerBlue; } - else if (Entity is Item) + else if (Entity is Item i) { + if (i.Submarine != null && i.GetComponent() == null) + { + // Don't show items that are inside the submarine, because monsters shouldn't target them when they are inside and the monsters are outside. + return; + } color = Color.CadetBlue; } else diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index c5b38a31e..893fb4761 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -1,12 +1,12 @@ using Barotrauma.Extensions; using Barotrauma.Networking; -using System; +using System; using System.Linq; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.Xml.Linq; -using System.IO; +using Barotrauma.IO; using Barotrauma.Items.Components; namespace Barotrauma @@ -624,7 +624,7 @@ namespace Barotrauma ch.ChangeSavedStatValue((StatTypes)statType, statValue, statIdentifier, removeOnDeath); } ch.ExperiencePoints = inc.ReadUInt16(); - + ch.AdditionalTalentPoints = inc.ReadUInt16(); return ch; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 73e11cd6a..d6443aee0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -422,6 +422,8 @@ namespace Barotrauma } } } + + healthWindowVerticalLayout.Recalculate(); } private void OnAttacked(Character attacker, AttackResult attackResult) @@ -950,6 +952,16 @@ namespace Barotrauma UpdateAlignment(); } + foreach (Affliction affliction in afflictions) + { + if (affliction.Prefab.AfflictionOverlay != null) + { + Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay; + ScreenAfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * (affliction.GetAfflictionOverlayMultiplier()), Vector2.Zero, 0.0f, + new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y)); + } + } + float damageOverlayAlpha = DamageOverlayTimer; if (Vitality < MaxVitality * 0.1f) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs index 8e5b77664..4966580c8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs @@ -5,8 +5,8 @@ using System.Linq; namespace Barotrauma { public class GUIFrame : GUIComponent - { - public int OutlineThickness { get; set; } + { + public float OutlineThickness { get; set; } public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index cd921c935..bb9e36562 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -49,7 +49,7 @@ namespace Barotrauma public UISprite ButtonPulse { get; private set; } public SpriteSheet FocusIndicator { get; private set; } - + public UISprite IconOverflowIndicator { get; private set; } /// @@ -462,7 +462,7 @@ namespace Barotrauma public void Apply(GUIComponent targetComponent, string styleName = "", GUIComponent parent = null) { - GUIComponentStyle componentStyle = null; + GUIComponentStyle componentStyle = null; if (parent != null) { GUIComponentStyle parentStyle = parent.Style; @@ -477,7 +477,7 @@ namespace Barotrauma return; } } - + string childStyleName = string.IsNullOrEmpty(styleName) ? targetComponent.GetType().Name : styleName; parentStyle.ChildStyles.TryGetValue(childStyleName.ToLowerInvariant(), out componentStyle); } @@ -493,8 +493,8 @@ namespace Barotrauma return; } } - - targetComponent.ApplyStyle(componentStyle); + + targetComponent.ApplyStyle(componentStyle); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index 70c0e9f76..efee168f0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -125,7 +125,7 @@ namespace Barotrauma public TabMenu() { - if (!initialized) Initialize(); + if (!initialized) { Initialize(); } CreateInfoFrame(selectedTab); SelectInfoFrameTab(null, selectedTab); @@ -260,6 +260,10 @@ namespace Barotrauma } var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.talents"); + talentsButton.OnAddedToGUIUpdateList += (GUIComponent component) => + { + talentsButton.Enabled = Character.Controlled?.Info != null && GameMain.GameSession?.Campaign != null; + }; } private bool SelectInfoFrameTab(GUIButton button, object userData) @@ -1177,8 +1181,6 @@ namespace Barotrauma private GUITextBlock talentPointsText; private GUITextBlock experienceText; - private Color experienceBackgroundColor = new Color(255, 255, 255, 155); - private GUIProgressBar experienceBar; private void CreateTalentInfo(GUIFrame infoFrame) @@ -1186,12 +1188,13 @@ namespace Barotrauma infoFrame.ClearChildren(); talentButtons.Clear(); + Character controlledCharacter = Character.Controlled; + if (controlledCharacter == null) { return; } + GUIFrame talentFrameBackground = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); int padding = GUI.IntScale(15); GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null); - Character controlledCharacter = Character.Controlled; - if (controlledCharacter.Info == null) { DebugConsole.ThrowError("No character info found for talent UI"); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index 00f03cf5b..f42c29829 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -51,7 +51,7 @@ namespace Barotrauma { keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length]; keyMapping[(int)InputType.Run] = new KeyOrMouse(Keys.LeftShift); - keyMapping[(int)InputType.Attack] = new KeyOrMouse(Keys.R); + keyMapping[(int)InputType.Attack] = new KeyOrMouse(Keys.F); keyMapping[(int)InputType.Crouch] = new KeyOrMouse(Keys.LeftControl); keyMapping[(int)InputType.Grab] = new KeyOrMouse(Keys.G); keyMapping[(int)InputType.Health] = new KeyOrMouse(Keys.H); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 919a36ab8..1e365da6b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -688,6 +688,11 @@ namespace Barotrauma { break; } + //if putting an item to a container with a max stack size of 1, only put one item from the stack + if (quickUseAction == QuickUseAction.PutToContainer && (character.SelectedConstruction?.GetComponent()?.MaxStackSize ?? 0) <= 1) + { + break; + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs index 981c388a2..37c6d9553 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs @@ -31,11 +31,23 @@ namespace Barotrauma.Items.Components name = TextManager.GetWithVariable("entityname.taintedgeneticmaterial", "[geneticmaterialname]", name); } - if (TextManager.ContainsTag("entitydescription."+Item.prefab.Identifier)) + if (TextManager.ContainsTag("entitydescription." + Item.prefab.Identifier)) { - int value = (int)MathHelper.Lerp(TooltipValueMin, TooltipValueMax, item.ConditionPercentage / 100.0f); + int value = (int)MathHelper.Lerp(TooltipValueMin, TooltipValueMax, item.ConditionPercentage / 100.0f); description = TextManager.GetWithVariable("entitydescription." + Item.prefab.Identifier, "[value]", value.ToString()); } + foreach (Item containedItem in item.ContainedItems) + { + var containedGeneticMaterial = containedItem.GetComponent(); + if (containedGeneticMaterial == null) { continue; } + string _ = string.Empty; + string containedDescription = containedItem.Description; + containedGeneticMaterial.AddTooltipInfo(ref _, ref containedDescription); + if (!string.IsNullOrEmpty(containedDescription)) + { + description += '\n' + containedDescription; + } + } } public void ModifyDeconstructInfo(Deconstructor deconstructor, ref string buttonText, ref string infoText) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index 4a1049947..5b054daff 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -19,6 +19,8 @@ namespace Barotrauma.Items.Components /// private float[] containedSpriteDepths; + private Sprite[] slotIcons; + public Sprite InventoryTopSprite { get { return inventoryTopSprite; } @@ -88,6 +90,7 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(XElement element) { + slotIcons = new Sprite[capacity]; foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -107,6 +110,17 @@ namespace Barotrauma.Items.Components case "containedstateindicatorempty": ContainedStateIndicatorEmpty = new Sprite(subElement); break; + case "sloticon": + int index = subElement.GetAttributeInt("slotindex", -1); + Sprite icon = new Sprite(subElement); + for (int i = 0; i < capacity; i++) + { + if (i == index || index == -1) + { + slotIcons[i] = icon; + } + } + break; } } @@ -208,6 +222,12 @@ namespace Barotrauma.Items.Components } } + public Sprite GetSlotIcon(int slotIndex) + { + if (slotIndex < 0 || slotIndex >= slotIcons.Length) { return null; } + return slotIcons[slotIndex]; + } + public bool KeepOpenWhenEquippedBy(Character character) { if (!character.CanAccessInventory(Inventory) || diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index 62c485a67..cf62a7d76 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -24,6 +24,9 @@ namespace Barotrauma.Items.Components [Serialize("DeconstructorDeconstruct", true)] public string ActivateButtonText { get; set; } + [Serialize("", true)] + public string InfoText { get; set; } + [Serialize(0.0f, true)] public float InfoAreaWidth { get; set; } @@ -102,8 +105,8 @@ namespace Barotrauma.Items.Components var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; - // === OUTPUT SLOTS === // - outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null); + // === OUTPUT SLOTS === // + outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null); if (InfoAreaWidth >= 0.0f) { @@ -114,10 +117,18 @@ namespace Barotrauma.Items.Components ActivateButton.OnAddedToGUIUpdateList += (GUIComponent component) => { activateButton.Enabled = true; - infoArea.Text = string.Empty; + if (string.IsNullOrEmpty(InfoText)) + { + infoArea.Text = string.Empty; + } + else + { + infoArea.Text = TextManager.Get(InfoText, returnNull: true) ?? InfoText; + } if (IsActive) { activateButton.Text = TextManager.Get("DeconstructorCancel"); + infoArea.Text = string.Empty; return; } bool outputsFound = false; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index b0aee591b..7c64d0346 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -12,24 +12,24 @@ namespace Barotrauma.Items.Components { internal readonly struct MiniMapGUIComponent { - public readonly GUIComponent Component; + public readonly GUIComponent RectComponent; public readonly GUIComponent BorderComponent; - public MiniMapGUIComponent(GUIComponent component) + public MiniMapGUIComponent(GUIComponent rectComponent) { - Component = component; - BorderComponent = component; + RectComponent = rectComponent; + BorderComponent = rectComponent; } public MiniMapGUIComponent(GUIComponent frame, GUIComponent linkedHullComponent) { - Component = frame; + RectComponent = frame; BorderComponent = linkedHullComponent; } public void Deconstruct(out GUIComponent component, out GUIComponent borderComponent) { - component = Component; + component = RectComponent; borderComponent = BorderComponent; } } @@ -183,6 +183,7 @@ namespace Barotrauma.Items.Components private ImmutableDictionary hullStatusComponents; private ImmutableDictionary electricalMapComponents; private ImmutableDictionary electricalChildren; + private ImmutableDictionary doorChildren; private ImmutableHashSet itemsFoundOnSub; @@ -190,7 +191,7 @@ namespace Barotrauma.Items.Components private float blipState; private const float maxBlipState = 1f; - private const float maxZoom = 2f, + private const float maxZoom = 10f, minZoom = 0.5f, defaultZoom = 1f; @@ -209,13 +210,15 @@ namespace Barotrauma.Items.Components private bool recalculate; - public static readonly Color MiniMapBaseColor = Color.DarkCyan; + public static readonly Color MiniMapBaseColor = new Color(15, 178, 107); - private static readonly Color WetHullColor = new Color(9, 80, 159), + private static readonly Color WetHullColor = new Color(11, 122, 205), + DoorIndicatorColor = GUI.Style.Green, + NoPowerDoorColor = DoorIndicatorColor * 0.1f, DefaultNeutralColor = MiniMapBaseColor * 0.8f, HoverColor = Color.White, - BlueprintBlue = new Color(48, 87, 255), - HullWaterColor = new Color(85, 136, 147), + BlueprintBlue = new Color(23, 38, 33), + HullWaterColor = new Color(17, 173, 179), HullWaterLineColor = Color.LightBlue, NoPowerColor = MiniMapBaseColor * 0.1f, ElectricalBaseColor = GUI.Style.Orange, @@ -323,7 +326,7 @@ namespace Barotrauma.Items.Components CanBeFocused = false }; - SetTooltipPosition(searchAutoComplete, searchBar); + SetAutoCompletePosition(searchAutoComplete, searchBar); GUIListBox listBox = new GUIListBox(new RectTransform(Vector2.One, searchAutoComplete.RectTransform)) { @@ -403,16 +406,17 @@ namespace Barotrauma.Items.Components scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center)); miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false }; - miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, null, out hullStatusComponents); + ImmutableHashSet hullPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && it.GetComponent() != null).ToImmutableHashSet(); + miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, hullPointsOfInterest, out hullStatusComponents); - IEnumerable pointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent() != null); - electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), pointsOfInterest, out electricalMapComponents); + IEnumerable electrialPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent() != null); + electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), electrialPointsOfInterest, out electricalMapComponents); Dictionary electricChildren = new Dictionary(); foreach (var (entity, component) in electricalMapComponents) { - GUIComponent parent = component.Component; + GUIComponent parent = component.RectComponent; if (!(entity is Item it )) { continue; } Sprite? sprite = it.Prefab.UpgradePreviewSprite; if (sprite is null) { continue; } @@ -430,6 +434,31 @@ namespace Barotrauma.Items.Components electricalChildren = electricChildren.ToImmutableDictionary(); + Dictionary doorChilds = new Dictionary(); + + foreach (var (entity, component) in hullStatusComponents) + { + if (!hullPointsOfInterest.Contains(entity)) { continue; } + + const int minSize = 8; + const int borderMaxSize = 2; + + Point size = component.BorderComponent.Rect.Size; + + size.X = Math.Max(size.X, minSize); + size.Y = Math.Max(size.Y, minSize); + float width = Math.Min(borderMaxSize, Math.Min(size.X, size.Y) / 8f); + + GUIFrame frame = new GUIFrame(new RectTransform(size, component.RectComponent.RectTransform, anchor: Anchor.Center), style: "ScanLines", color: DoorIndicatorColor) + { + OutlineColor = GUI.Style.Green, + OutlineThickness = width + }; + doorChilds.Add(component, frame); + } + + doorChildren = doorChilds.ToImmutableDictionary(); + Rectangle parentRect = miniMapFrame.Rect; displayedSubs.Clear(); @@ -466,7 +495,6 @@ namespace Barotrauma.Items.Components } } - if (currentMode != MiniMapMode.HullStatus && Math.Abs(PlayerInput.ScrollWheelSpeed) > 0 && (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn))) { float newZoom = Math.Clamp(Zoom + PlayerInput.ScrollWheelSpeed / 1000.0f * Zoom, minZoom, maxZoom); @@ -501,10 +529,10 @@ namespace Barotrauma.Items.Components miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint(); recalculate = false; - var (maxWidth, maxHeight) = miniMapContainer.Rect.Size.ToVector2() / 2f / Zoom; - - mapOffset.X = Math.Clamp(mapOffset.X, -maxWidth, maxWidth); - mapOffset.Y = Math.Clamp(mapOffset.Y, -maxHeight, maxHeight); + // var (maxWidth, maxHeight) = miniMapContainer.Rect.Size.ToVector2() / 2f; + // + // mapOffset.X = Math.Clamp(mapOffset.X, -maxWidth, maxWidth); + // mapOffset.Y = Math.Clamp(mapOffset.Y, -maxHeight, maxHeight); } // is there a better way to do this? @@ -610,7 +638,7 @@ namespace Barotrauma.Items.Components { if (!(entity is Hull hull)) { continue; } if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } - DrawHullCards(spriteBatch, hull, hullData, component.Component); + DrawHullCards(spriteBatch, hull, hullData, component.RectComponent); } spriteBatch.End(); @@ -645,7 +673,7 @@ namespace Barotrauma.Items.Components MiniMapBlips = null; searchedPrefab = null; searchAutoComplete.Visible = true; - SetTooltipPosition(searchAutoComplete, box); + SetAutoCompletePosition(searchAutoComplete, box); GUIListBox listBox = searchAutoComplete.GetChild(); if (listBox is null) { return false; } @@ -677,7 +705,7 @@ namespace Barotrauma.Items.Components return true; } - private void SetTooltipPosition(GUIComponent tooltip, GUITextBox box) + private void SetAutoCompletePosition(GUIComponent tooltip, GUITextBox box) { int height = GuiFrame.Rect.Height / 2; tooltip.RectTransform.NonScaledSize = new Point(box.Rect.Width, height); @@ -707,7 +735,6 @@ namespace Barotrauma.Items.Components { if (searchedPrefab is null) { - Console.WriteLine("Bruh"); ItemPrefab? first = ItemPrefab.Prefabs.FirstOrDefault(p => p.Name.ToLower().Equals(text.ToLower())); if (first is null) @@ -790,12 +817,39 @@ namespace Barotrauma.Items.Components private void UpdateHullStatus() { + bool canHoverOverHull = true; + + foreach (var (entity, component) in hullStatusComponents) + { + // we are only interested in non-hull components + if (entity is Hull) { continue; } + + GUIComponent rectComponent = component.RectComponent; + + if (doorChildren.TryGetValue(component, out GUIComponent? child) && child != null) + { + if (item.Submarine == null || !hasPower) + { + child.Color = child.OutlineColor = NoPowerDoorColor; + } + + if (Voltage < MinVoltage) { continue; } + + child.Color = child.OutlineColor = DoorIndicatorColor; + if (GUI.MouseOn == child) + { + SetTooltip(rectComponent.Rect.Center, entity.Name, string.Empty, string.Empty, string.Empty); + canHoverOverHull = false; + child.Color = child.OutlineColor = HoverColor; + } + } + } + foreach (var (entity, (component, borderComponent)) in hullStatusComponents) { if (item.Submarine == null || !hasPower) { - component.Color = NoPowerColor; - borderComponent.OutlineColor = NoPowerColor; + component.Color = borderComponent.OutlineColor = NoPowerColor; } if (Voltage < MinVoltage) { continue; } @@ -848,7 +902,7 @@ namespace Barotrauma.Items.Components borderColor = Color.Lerp(neutralColor, GUI.Style.Red, Math.Min(gapOpenSum, 1.0f)); } - bool isHoveringOver = GUI.MouseOn == component; + bool isHoveringOver = canHoverOverHull && GUI.MouseOn == component; // When drawing tooltip we are only interested in the component we are hovering over if (isHoveringOver) @@ -888,7 +942,7 @@ namespace Barotrauma.Items.Components { if (!hullStatusComponents.ContainsKey(linkedHull)) { continue; } - isHoveringOver |= hullStatusComponents[linkedHull].Component == GUI.MouseOn; + isHoveringOver |= canHoverOverHull && hullStatusComponents[linkedHull].RectComponent == GUI.MouseOn; if (isHoveringOver) { break; } } @@ -919,7 +973,7 @@ namespace Barotrauma.Items.Components component.Color = component.OutlineColor = NoPowerElectricalColor; } - if (Voltage < MinVoltage || !miniMapGuiComponent.Component.Visible) { continue; } + if (Voltage < MinVoltage || !miniMapGuiComponent.RectComponent.Visible) { continue; } int durability = (int)(it.Condition / it.MaxCondition * 100f); Color color = ToolBox.GradientLerp(durability / 100f, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green, GUI.Style.Green); @@ -956,9 +1010,6 @@ namespace Barotrauma.Items.Components { if (item.Submarine != null) { - Rectangle parentRect = container.Rect; - if (miniMapFrame is { } miniMap) { parentRect = miniMap.Rect; } - DrawSubmarine(spriteBatch); } @@ -995,7 +1046,7 @@ namespace Barotrauma.Items.Components if (hullData.Distort) { continue; } - GUIComponent hullFrame = component.Component; + GUIComponent hullFrame = component.RectComponent; if (hullsVisible && hullData.HullWaterAmount is { } waterAmount) { @@ -1197,10 +1248,12 @@ namespace Barotrauma.Items.Components const float padding = 8f; float totalWidth = 0f; + float parentWidth = submarineContainer.Rect.Width / 24f; + int i = 0; foreach (MiniMapSprite info in cardsToDraw) { - float spriteSize = info.Sprite.size.X * (frame.Rect.Height / info.Sprite.size.Y) + padding; + float spriteSize = info.Sprite.size.X * (parentWidth / info.Sprite.size.X) + padding; if (totalWidth + spriteSize > frame.Rect.Width) { break; } totalWidth += spriteSize; @@ -1213,10 +1266,11 @@ namespace Barotrauma.Items.Components float offset = 0; int amount = 0; + foreach (MiniMapSprite info in cardsToDraw) { Sprite sprite = info.Sprite; - float scale = frame.Rect.Height / sprite.size.Y; + float scale = parentWidth / sprite.size.X; float spriteSize = sprite.size.X * scale; float posX = adjustedCenterX + offset; @@ -1243,7 +1297,7 @@ namespace Barotrauma.Items.Components float halfSize = spriteSize / 2f; if (i > 0) { offset += halfSize; } Vector2 pos = new Vector2(adjustedCenterX + offset, centerY); - sprite.Draw(spriteBatch, pos, info.Color, scale: scale, origin: sprite.size / 2f); + sprite.Draw(spriteBatch, pos, info.Color * 0.8f, scale: scale, origin: sprite.size / 2f); offset += halfSize + padding; amount++; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index dbf89512b..780de4f8b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -446,13 +446,8 @@ namespace Barotrauma.Items.Components zoomSlider.BarScroll += PlayerInput.ScrollWheelSpeed / 1000.0f; zoomSlider.OnMoved(zoomSlider, zoomSlider.BarScroll); } - - if (PlayerInput.KeyHit(InputType.Run)) - { - SonarModeSwitch.OnClicked(SonarModeSwitch, null); - } } - + float distort = 1.0f - item.Condition / item.MaxCondition; for (int i = sonarBlips.Count - 1; i >= 0; i--) { @@ -1634,7 +1629,7 @@ namespace Barotrauma.Items.Components void CalculateDistance() { - pathFinder ??= new PathFinder(WayPoint.WayPointList, indoorsSteering: false); + pathFinder ??= new PathFinder(WayPoint.WayPointList, false); var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(transducerPosition), ConvertUnits.ToSimUnits(worldPosition)); if (!path.Unreachable) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index a770a45cb..a960ec531 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -244,7 +244,7 @@ namespace Barotrauma.Items.Components sabotageButtonText : sabotagingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); - TinkerButton.Visible = CanTinker(character); + TinkerButton.Visible = IsTinkerable(character); TinkerButton.IgnoreLayoutGroups = !TinkerButton.Visible; TinkerButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Tinker)) && CanTinker(character); TinkerButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Tinker && CanTinker(character)) ? @@ -303,6 +303,7 @@ namespace Barotrauma.Items.Components deteriorationTimer = msg.ReadSingle(); deteriorateAlwaysResetTimer = msg.ReadSingle(); DeteriorateAlways = msg.ReadBoolean(); + tinkeringDuration = msg.ReadSingle(); ushort currentFixerID = msg.ReadUInt16(); currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index a4cc3788d..a91cd1cf9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -1316,6 +1316,15 @@ namespace Barotrauma { selectedSlot = null; } + var parentItem = (selectedSlot?.ParentInventory?.Owner as Item) ?? selectedSlot?.Item; + if ((parentItem?.GetRootInventoryOwner() is Character ownerCharacter) && + ownerCharacter == Character.Controlled && + CharacterHealth.OpenHealthWindow?.Character != ownerCharacter && + ownerCharacter.Inventory.IsInLimbSlot(parentItem, InvSlotType.HealthInterface)) + { + highlightedSubInventorySlots.RemoveWhere(s => s.Item == parentItem); + selectedSlot = null; + } } } } @@ -1394,7 +1403,7 @@ namespace Barotrauma float scale = Math.Min(Math.Min(iconSize / sprite.size.X, iconSize / sprite.size.Y), 1.5f); Vector2 itemPos = PlayerInput.MousePosition; - bool mouseOnHealthInterface = CharacterHealth.OpenHealthWindow != null && CharacterHealth.OpenHealthWindow.MouseOnElement; + bool mouseOnHealthInterface = CharacterHealth.OpenHealthWindow != null && CharacterHealth.OpenHealthWindow.MouseOnElement && DraggingItems.Any(it => it.UseInHealthInterface); if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null) { @@ -1453,7 +1462,8 @@ namespace Barotrauma } Color slotColor = Color.White; - if (inventory?.Owner is Item i && !i.IsPlayerTeamInteractable) { slotColor = Color.Gray; } + Item parentItem = inventory?.Owner as Item; + if (parentItem != null && !parentItem.IsPlayerTeamInteractable) { slotColor = Color.Gray; } var itemContainer = item?.GetComponent(); if (itemContainer != null && (itemContainer.InventoryTopSprite != null || itemContainer.InventoryBottomSprite != null)) { @@ -1576,6 +1586,14 @@ namespace Barotrauma pulsate: !usingDefaultSprite && containedState >= 0.0f && containedState < 0.25f && inventory == Character.Controlled?.Inventory && Character.Controlled.HasEquippedItem(item)); } } + else + { + var slotIcon = parentItem?.GetComponent()?.GetSlotIcon(slotIndex); + if (slotIcon != null) + { + slotIcon.Draw(spriteBatch, rect.Center.ToVector2(), GUI.Style.EquipmentSlotIconColor, scale: Math.Min(rect.Width / slotIcon.size.X, rect.Height / slotIcon.size.Y) * 0.8f); + } + } } if (GameMain.DebugDraw) @@ -1609,7 +1627,7 @@ namespace Barotrauma Color spriteColor = sprite == item.Sprite ? item.GetSpriteColor() : item.GetInventoryIconColor(); if (inventory != null && (inventory.Locked || inventory.slots[slotIndex].Items.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))) { spriteColor *= 0.5f; } - if (CharacterHealth.OpenHealthWindow != null && !item.UseInHealthInterface) + if (CharacterHealth.OpenHealthWindow != null && !item.UseInHealthInterface && !item.AllowedSlots.Contains(InvSlotType.HealthInterface) && item.GetComponent() == null) { spriteColor = Color.Lerp(spriteColor, Color.TransparentBlack, 0.5f); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs index 5854fc17b..d81332c48 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs @@ -77,6 +77,13 @@ namespace Barotrauma protected set; } + [Serialize(true, false)] + public bool ShowInStatusMonitor + { + get; + private set; + } + [Serialize("", false)] public string ImpactSoundTag { get; private set; } @@ -84,13 +91,13 @@ namespace Barotrauma public override void UpdatePlacing(Camera cam) { Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); - + if (PlayerInput.SecondaryMouseButtonClicked()) { selected = null; return; } - + var potentialContainer = MapEntity.GetPotentialContainer(position); if (!ResizeHorizontal && !ResizeVertical) @@ -155,7 +162,7 @@ namespace Barotrauma { potentialContainer.IsHighlighted = true; } - + //if (PlayerInput.GetMouseState.RightButton == ButtonState.Pressed) selected = null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index a6244b448..d0547cdbe 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs @@ -993,6 +993,10 @@ namespace Barotrauma clone.Move(moveAmount); clone.Submarine = Submarine.MainSub; } + foreach (MapEntity clone in SelectedList) + { + (clone as Item)?.GetComponent()?.SetContainedItemPositions(); + } SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false, handleInventoryBehavior: false)); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs index 85da43a34..8e046d7ab 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs @@ -127,6 +127,13 @@ namespace Barotrauma ID.ToString(), new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30), color); + if (Tunnel?.Type != null) + { + GUI.SmallFont.DrawString(spriteBatch, + Tunnel.Type.ToString(), + new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 45), + color); + } } public override bool IsMouseOn(Vector2 position) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs index 5de8e14c5..4cd0d5e84 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs @@ -494,7 +494,26 @@ namespace Barotrauma.Networking stream?.Close(); break; case FileTransferType.CampaignSave: - //TODO: verify that the received file is a valid save file + try + { + var files = SaveUtil.EnumerateContainedFiles(fileTransfer.FilePath); + foreach (var file in files) + { + string extension = Path.GetExtension(file); + if ((!extension.Equals(".sub", StringComparison.OrdinalIgnoreCase) + && !file.Equals("gamesession.xml")) + || file.CleanUpPathCrossPlatform(correctFilenameCase: false).Contains('/')) + { + ErrorMessage = $"Found unexpected file in \"{fileTransfer.FileName}\"! ({file})"; + return false; + } + } + } + catch (Exception e) + { + ErrorMessage = $"Loading received campaign save \"{fileTransfer.FileName}\" failed! {{{e.Message}}}"; + return false; + } break; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EditorImage.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EditorImage.cs index 4563b2ff6..dab80c527 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/EditorImage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EditorImage.cs @@ -1,7 +1,7 @@ #nullable enable using System; using System.Collections.Generic; -using System.IO; +using Barotrauma.IO; using System.Linq; using System.Xml.Linq; using Microsoft.Xna.Framework; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs index 050fd29eb..a806460f5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs @@ -56,11 +56,6 @@ namespace Barotrauma MiniMap miniMap = miniMapItem.GetComponent(); miniMap.PowerConsumption = 0; - foreach (var hull in Hull.hullList) - { - hull.WaterVolume = hull.Volume / 2f; - } - dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false); dummyCharacter.Info.Name = "Galldren"; dummyCharacter.Inventory.CreateSlots(); diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 1c1a72de1..002617924 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.1.0 + 0.1500.2.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 34e9c17cd..8ed8a8db7 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.1.0 + 0.1500.2.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 72f5373d5..f2ef1049a 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.1.0 + 0.1500.2.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 98e625016..32989bc6e 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.1.0 + 0.1500.2.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index bcaa8358d..a2648dcd0 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.1.0 + 0.1500.2.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index e5863ea30..ac69ac86c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -74,6 +74,7 @@ namespace Barotrauma } } msg.Write((ushort)ExperiencePoints); + msg.Write((ushort)AdditionalTalentPoints); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs index 3cf56ec69..19f03f8c3 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs @@ -38,6 +38,7 @@ namespace Barotrauma.Items.Components msg.Write(deteriorationTimer); msg.Write(deteriorateAlwaysResetTimer); msg.Write(DeteriorateAlways); + msg.Write(tinkeringDuration); msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID); msg.WriteRangedInteger((int)currentFixerAction, 0, 2); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index affe37a85..90271e6e3 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -163,9 +163,7 @@ namespace Barotrauma.Networking RadiationEnabled = incMsg.ReadBoolean(); int maxMissionCount = MaxMissionCount + incMsg.ReadByte() - 1; - if (maxMissionCount < CampaignSettings.MinMissionCountLimit) maxMissionCount = CampaignSettings.MaxMissionCountLimit; - if (maxMissionCount > CampaignSettings.MaxMissionCountLimit) maxMissionCount = CampaignSettings.MinMissionCountLimit; - MaxMissionCount = maxMissionCount; + MaxMissionCount = MathHelper.Clamp(maxMissionCount, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); changed |= true; } diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index e4aa06d3c..4d2b9f8d8 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.1.0 + 0.1500.2.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 168edfa9e..3635b8e7e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -1,5 +1,6 @@ using Barotrauma.Items.Components; using Barotrauma.Networking; +using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -95,14 +96,26 @@ namespace Barotrauma } } - protected bool HasValidPath(bool requireNonDirty = false) => - steeringManager is IndoorsSteeringManager pathSteering && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable && (!requireNonDirty || !pathSteering.IsPathDirty); + public bool HasValidPath(bool requireNonDirty = false, bool requireUnfinished = true) => + steeringManager is IndoorsSteeringManager pathSteering && + pathSteering.CurrentPath != null && + (!requireUnfinished || !pathSteering.CurrentPath.Finished) && + !pathSteering.CurrentPath.Unreachable && + (!requireNonDirty || !pathSteering.IsPathDirty); + + protected readonly float colliderWidth; + protected readonly float colliderLength; + protected readonly float avoidLookAheadDistance; public AIController (Character c) { Character = c; hullVisibilityTimer = Rand.Range(0f, hullVisibilityTimer); Enabled = true; + var size = Character.AnimController.Collider.GetSize(); + colliderWidth = size.X; + colliderLength = size.Y; + avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f); } public virtual void OnAttacked(Character attacker, AttackResult attackResult) { } @@ -327,6 +340,119 @@ namespace Barotrauma unequippedItems.Clear(); } + #region Escape + public abstract void Escape(float deltaTime); + + public Gap EscapeTarget { get; private set; } + + private readonly float escapeTargetSeekInterval = 2; + private float escapeTimer; + protected bool allGapsSearched; + protected readonly HashSet unreachableGaps = new HashSet(); + protected bool UpdateEscape(float deltaTime, bool canAttackDoors) + { + IndoorsSteeringManager pathSteering = SteeringManager as IndoorsSteeringManager; + if (allGapsSearched) + { + escapeTimer -= deltaTime; + if (escapeTimer <= 0) + { + allGapsSearched = false; + } + } + if (Character.CurrentHull != null && pathSteering != null) + { + // Seek exit if inside + if (!allGapsSearched) + { + float closestDistance = 0; + foreach (Gap gap in Gap.GapList) + { + if (gap == null || gap.Removed) { continue; } + if (EscapeTarget == gap) { continue; } + if (unreachableGaps.Contains(gap)) { continue; } + if (gap.Submarine != Character.Submarine) { continue; } + if (gap.IsRoomToRoom) { continue; } + float multiplier = 1; + var door = gap.ConnectedDoor; + if (door != null) + { + if (!door.CanBeTraversed) + { + if (!door.HasAccess(Character)) + { + if (!canAttackDoors) { continue; } + // Treat doors that don't have access to like they were farther, because it will take time to break them. + multiplier = 5; + } + } + } + else + { + if (gap.Open < 1) { continue; } + bool canGetThrough = ConvertUnits.ToDisplayUnits(colliderWidth) < gap.Size; + if (!canGetThrough) { continue; } + } + if (gap.FlowTargetHull == Character.CurrentHull) + { + // If the gap is in the same room, it's close enough. + EscapeTarget = gap; + break; + } + float distance = Vector2.DistanceSquared(Character.WorldPosition, gap.WorldPosition) * multiplier; + if (EscapeTarget == null || distance < closestDistance) + { + EscapeTarget = gap; + closestDistance = distance; + } + } + allGapsSearched = true; + escapeTimer = escapeTargetSeekInterval; + } + else if (EscapeTarget != null && EscapeTarget.FlowTargetHull != Character.CurrentHull) + { + if (pathSteering.CurrentPath != null && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) + { + unreachableGaps.Add(EscapeTarget); + EscapeTarget = null; + allGapsSearched = false; + } + } + } + if (EscapeTarget != null) + { + SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10); + float sqrDist = Vector2.DistanceSquared(Character.SimPosition, EscapeTarget.SimPosition); + if (sqrDist < 0.5f || Character.CurrentHull == null || HasValidPath(requireNonDirty: true, requireUnfinished: false) && pathSteering.CurrentPath.Finished) + { + // Very close to the target, outside, or at the end of the path -> just steer towards it manually without using the path + SteeringManager.Reset(); + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(EscapeTarget.WorldPosition - Character.WorldPosition)); + if (sqrDist < 4) + { + return true; + } + } + } + else + { + // Can't find the target + EscapeTarget = null; + allGapsSearched = false; + unreachableGaps.Clear(); + } + return false; + } + + public void ResetEscape() + { + EscapeTarget = null; + allGapsSearched = false; + unreachableGaps.Clear(); + } + + #endregion + protected virtual void OnStateChanged(AIState from, AIState to) { } protected virtual void OnTargetChanged(AITarget previousTarget, AITarget newTarget) { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index a448481c0..241cd47a8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -60,8 +60,6 @@ namespace Barotrauma // Min priority for the memorized targets. The actual value fades gradually, unless kept fresh by selecting the target. private const float minPriority = 10; - private readonly float avoidLookAheadDistance; - private IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager; private SteeringManager outsideSteering, insideSteering; @@ -113,20 +111,21 @@ namespace Barotrauma lastAttackUpdateTime = Timing.TotalTime; } } - + + public AITargetMemory SelectedTargetMemory => selectedTargetMemory; private AITargetMemory selectedTargetMemory; private float targetValue; private CharacterParams.TargetParams selectedTargetingParams; private Dictionary targetMemories; - private readonly float colliderWidth; - private readonly float colliderLength; private readonly int requiredHoleCount; private bool canAttackWalls; + public bool CanAttackDoors => canAttackDoors; private bool canAttackDoors; private bool canAttackCharacters; + public float PriorityFearIncrement => priorityFearIncreasement; private readonly float priorityFearIncreasement = 2; private readonly float memoryFadeTime = 0.5f; @@ -299,12 +298,8 @@ namespace Barotrauma steeringManager = outsideSteering; State = AIState.Idle; - var size = Character.AnimController.Collider.GetSize(); - colliderWidth = size.X; - colliderLength = size.Y; requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize); - avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f); myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody); } @@ -440,9 +435,9 @@ namespace Barotrauma if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer || Character.Controlled == Character)) { - if (SelectedAiTarget?.Entity != null || escapeTarget != null) + if (SelectedAiTarget?.Entity != null || EscapeTarget != null) { - Entity t = SelectedAiTarget?.Entity ?? escapeTarget; + Entity t = SelectedAiTarget?.Entity ?? EscapeTarget; float referencePos = Vector2.DistanceSquared(Character.WorldPosition, t.WorldPosition) > 100 * 100 && HasValidPath(true) ? PathSteering.CurrentPath.CurrentNode.WorldPosition.X : t.WorldPosition.X; Character.AnimController.TargetDir = Character.WorldPosition.X < referencePos ? Direction.Right : Direction.Left; } @@ -585,7 +580,7 @@ namespace Barotrauma case AIState.Escape: case AIState.Flee: run = true; - UpdateEscape(deltaTime); + Escape(deltaTime); break; case AIState.Avoid: case AIState.PassiveAggressive: @@ -602,7 +597,7 @@ namespace Barotrauma run = true; if (State == AIState.Avoid) { - UpdateEscape(deltaTime); + Escape(deltaTime); } else { @@ -827,142 +822,6 @@ namespace Barotrauma #endregion - #region Escape - private readonly float escapeTargetSeekInterval = 2; - private float escapeTimer; - private Gap escapeTarget; - private bool allGapsSearched; - private readonly HashSet unreachableGaps = new HashSet(); - private void UpdateEscape(float deltaTime) - { - if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)) - { - State = AIState.Idle; - return; - } - else if (selectedTargetMemory != null && SelectedAiTarget?.Entity is Character) - { - selectedTargetMemory.Priority += deltaTime * priorityFearIncreasement; - } - IndoorsSteeringManager pathSteering = SteeringManager as IndoorsSteeringManager; - bool hasValidPath = pathSteering?.CurrentPath != null && !pathSteering.IsPathDirty && !pathSteering.CurrentPath.Unreachable; - if (allGapsSearched) - { - escapeTimer -= deltaTime; - if (escapeTimer <= 0) - { - allGapsSearched = false; - } - } - if (Character.CurrentHull != null && pathSteering != null) - { - // Seek exit if inside - if (!allGapsSearched) - { - float closestDistance = 0; - foreach (Gap gap in Gap.GapList) - { - if (gap == null || gap.Removed) { continue; } - if (escapeTarget == gap) { continue; } - if (unreachableGaps.Contains(gap)) { continue; } - if (gap.Submarine != Character.Submarine) { continue; } - if (gap.IsRoomToRoom) { continue; } - float multiplier = 1; - var door = gap.ConnectedDoor; - if (door != null) - { - if (!door.CanBeTraversed) - { - if (!door.HasAccess(Character)) - { - if (!canAttackDoors) { continue; } - // Treat doors that don't have access to like they were farther, because it will take time to break them. - multiplier = 5; - } - } - } - else - { - if (gap.Open < 1) { continue; } - bool canGetThrough = ConvertUnits.ToDisplayUnits(colliderWidth) < gap.Size; - if (!canGetThrough) { continue; } - } - if (gap.FlowTargetHull == Character.CurrentHull) - { - // If the gap is in the same room, it's close enough. - escapeTarget = gap; - break; - } - float distance = Vector2.DistanceSquared(Character.WorldPosition, gap.WorldPosition) * multiplier; - if (escapeTarget == null || distance < closestDistance) - { - escapeTarget = gap; - closestDistance = distance; - } - } - allGapsSearched = true; - escapeTimer = escapeTargetSeekInterval; - } - else if (escapeTarget != null && escapeTarget.FlowTargetHull != Character.CurrentHull) - { - if (pathSteering.CurrentPath != null && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) - { - unreachableGaps.Add(escapeTarget); - escapeTarget = null; - allGapsSearched = false; - } - } - } - if (escapeTarget != null && Character.CurrentHull != null && Vector2.DistanceSquared(Character.SimPosition, escapeTarget.SimPosition) > 0.5f) - { - if (hasValidPath && pathSteering.CurrentPath.Finished) - { - // Steer manually towards the gap - SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(escapeTarget.WorldPosition - Character.WorldPosition)); - } - else if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull) - { - // Steer away from the target if in the same room - Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement); - if (!MathUtils.IsValid(escapeDir)) { escapeDir = Vector2.UnitY; } - SteeringManager.SteeringManual(deltaTime, escapeDir); - return; - } - else if (pathSteering != null) - { - if (hasValidPath && canAttackDoors) - { - var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor; - if (door != null && !door.CanBeTraversed && !door.HasAccess(Character)) - { - if (SelectedAiTarget != door.Item.AiTarget || State != AIState.Attack) - { - SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority); - State = AIState.Attack; - return; - } - } - } - } - SteeringManager.SteeringSeek(escapeTarget.SimPosition, 10); - } - else - { - escapeTarget = null; - allGapsSearched = false; - Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement); - if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY; - SteeringManager.SteeringManual(deltaTime, escapeDir); - if (Character.CurrentHull == null) - { - SteeringManager.SteeringWander(); - SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5); - } - } - } - - #endregion - #region Attack private Vector2 attackWorldPos; @@ -3129,11 +2988,9 @@ namespace Barotrauma { LatchOntoAI?.DeattachFromBody(reset: true); Character.AnimController.ReleaseStuckLimbs(); - escapeTarget = null; AttackingLimb = null; movementMargin = 0; - allGapsSearched = false; - unreachableGaps.Clear(); + ResetEscape(); if (isStateChanged && to == AIState.Idle && from != to) { SetStateResetTimer(); @@ -3283,6 +3140,71 @@ namespace Barotrauma public bool CanPassThroughHole(Structure wall, int sectionIndex) => CanPassThroughHole(wall, sectionIndex, requiredHoleCount); + public override void Escape(float deltaTime) + { + if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)) + { + State = AIState.Idle; + return; + } + else if (SelectedTargetMemory is AITargetMemory targetMemory && SelectedAiTarget?.Entity is Character) + { + targetMemory.Priority += deltaTime * PriorityFearIncrement; + } + bool isSteeringThroughGap = UpdateEscape(deltaTime, canAttackDoors); + if (!isSteeringThroughGap) + { + if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull) + { + SteerAwayFromTheEnemy(); + } + else if (canAttackDoors && HasValidPath(requireNonDirty: true, requireUnfinished: true)) + { + var door = PathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? PathSteering.CurrentPath.NextNode?.ConnectedDoor; + if (door != null && !door.CanBeTraversed && !door.HasAccess(Character)) + { + if (SelectedAiTarget != door.Item.AiTarget || State != AIState.Attack) + { + SelectTarget(door.Item.AiTarget, SelectedTargetMemory.Priority); + State = AIState.Attack; + } + } + } + } + if (EscapeTarget == null) + { + if (SelectedAiTarget?.Entity is Character) + { + SteerAwayFromTheEnemy(); + } + else + { + SteeringManager.SteeringWander(); + if (Character.CurrentHull == null) + { + SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5); + } + } + } + + void SteerAwayFromTheEnemy() + { + if (SelectedAiTarget == null) { return; } + Vector2 escapeDir = Vector2.Normalize(WorldPosition - SelectedAiTarget.WorldPosition); + if (Character.CurrentHull != null && !Character.AnimController.InWater) + { + // Inside + escapeDir = new Vector2(Math.Sign(escapeDir.X), 0); + } + if (!MathUtils.IsValid(escapeDir)) + { + escapeDir = Vector2.UnitY; + } + SteeringManager.Reset(); + SteeringManager.SteeringManual(deltaTime, escapeDir); + } + } + private readonly List targetLimbs = new List(); public Limb GetTargetLimb(Limb attackLimb, Character target, LimbType targetLimbType = LimbType.None) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index a67aa81f3..b3f381c2d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -20,6 +20,11 @@ namespace Barotrauma private float reactTimer; private float unreachableClearTimer; private bool shouldCrouch; + public bool IsInsideCave { get; private set; } + /// + /// Resets each frame + /// + public bool AutoFaceMovement = true; const float reactionTime = 0.3f; const float crouchRaycastInterval = 1; @@ -52,7 +57,7 @@ namespace Barotrauma private readonly float steeringBufferIncreaseSpeed = 100; private float steeringBuffer; - private readonly float obstacleRaycastInterval = 1; + private readonly float obstacleRaycastIntervalShort = 1, obstacleRaycastIntervalLong = 5; private float obstacleRaycastTimer; private readonly float enemyCheckInterval = 0.2f; @@ -86,6 +91,8 @@ namespace Barotrauma private readonly SteeringManager outsideSteering, insideSteering; + public bool UseIndoorSteeringOutside { get; set; } = false; + public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager; public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController; @@ -207,33 +214,78 @@ namespace Barotrauma IgnoredItems.Clear(); } - bool IsCloseEnoughToTargetSub(float threshold) => SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null && Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + threshold, 2); + bool IsCloseEnoughToTarget(float threshold, bool useTargetSub = true) + { + Entity target = SelectedAiTarget?.Entity; + if (target == null) + { + return false; + } + if (useTargetSub) + { + if (target.Submarine is Submarine sub) + { + target = sub; + threshold += Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2; + } + else + { + return false; + } + } + return Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition) < MathUtils.Pow(threshold, 2); + } + bool hasValidPath = HasValidPath(); if (Character.Submarine == null) { - if (hasValidPath) + // When the character is outside, far enough from the target, and the direct route is blocked, + // use the indoor steering with the main and side path waypoints to help avoid getting stuck in level walls + if (SelectedAiTarget?.Entity != null && !IsCloseEnoughToTarget(2000, useTargetSub: false)) { obstacleRaycastTimer -= deltaTime; if (obstacleRaycastTimer <= 0) { - obstacleRaycastTimer = obstacleRaycastInterval; - // Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs). - foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs()) + obstacleRaycastTimer = obstacleRaycastIntervalLong; + Vector2 rayEnd = SelectedAiTarget.Entity.SimPosition; + if (SelectedAiTarget.Entity.Submarine != null) { - if (connectedSub == Submarine.MainSub) { continue; } - Vector2 rayStart = SimPosition - connectedSub.SimPosition; - Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition; - Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5); - if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null) + rayEnd += SelectedAiTarget.Entity.Submarine.SimPosition; + } + UseIndoorSteeringOutside = Submarine.PickBody(SimPosition, rayEnd, collisionCategory: Physics.CollisionLevel) != null; + } + } + else + { + UseIndoorSteeringOutside = false; + if (hasValidPath) + { + obstacleRaycastTimer -= deltaTime; + if (obstacleRaycastTimer <= 0) + { + obstacleRaycastTimer = obstacleRaycastIntervalShort; + // Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs). + foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs()) { - PathSteering.CurrentPath.Unreachable = true; - break; + if (connectedSub == Submarine.MainSub) { continue; } + Vector2 rayStart = SimPosition - connectedSub.SimPosition; + Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition; + Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5); + if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null) + { + PathSteering.CurrentPath.Unreachable = true; + break; + } } } } } } + else + { + UseIndoorSteeringOutside = false; + } if (Character.Submarine == null || !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted) { @@ -273,13 +325,31 @@ namespace Barotrauma } } } - if (Character.Submarine != null || hasValidPath && IsCloseEnoughToTargetSub(maxSteeringBuffer) || IsCloseEnoughToTargetSub(steeringBuffer)) + + // Check whether the character is inside a cave + if (IsInsideCave) + { + // If the character was inside a cave, require them to move a bit further from the area to set the field back to false + // This is to avoid any twitchy behavior with the steering managers + IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => + { + var area = c.Area; + area.Inflate(new Vector2(100)); + return area.Contains(Character.WorldPosition); + }) is Level.Cave; + } + else + { + IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave; + } + + if (UseIndoorSteeringOutside || IsInsideCave || Character.Submarine != null || hasValidPath && IsCloseEnoughToTarget(maxSteeringBuffer) || IsCloseEnoughToTarget(steeringBuffer)) { if (steeringManager != insideSteering) { insideSteering.Reset(); + steeringManager = insideSteering; } - steeringManager = insideSteering; steeringBuffer += steeringBufferIncreaseSpeed * deltaTime; } else @@ -287,8 +357,8 @@ namespace Barotrauma if (steeringManager != outsideSteering) { outsideSteering.Reset(); + steeringManager = outsideSteering; } - steeringManager = outsideSteering; steeringBuffer = minSteeringBuffer; } steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer); @@ -419,7 +489,7 @@ namespace Barotrauma Character.SelectedConstruction.SecondaryUse(deltaTime, Character); } } - else if (Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater) + else if (AutoFaceMovement && Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater) { newDir = Character.AnimController.TargetMovement.X > 0.0f ? Direction.Right : Direction.Left; } @@ -429,6 +499,7 @@ namespace Barotrauma flipTimer = FlipInterval; } } + AutoFaceMovement = true; MentalStateManager?.Update(deltaTime); ShipCommandManager?.Update(deltaTime); @@ -1240,10 +1311,7 @@ namespace Barotrauma { var objective = new AIObjectiveCombat(Character, target, mode, objectiveManager) { - HoldPosition = - Character.Info?.Job?.Prefab.Identifier == "watchman" || - Character.CurrentHull == null || - Character.IsOnPlayerTeam && !target.IsPlayer && ObjectiveManager.GetActiveObjective()?.Target is Character followTarget && followTarget.IsPlayer, + HoldPosition = Character.Info?.Job?.Prefab.Identifier == "watchman", AbortCondition = abortCondition, allowHoldFire = allowHoldFire, }; @@ -1293,6 +1361,11 @@ namespace Barotrauma ObjectiveManager.WaitTimer = waitDuration; } + public override void Escape(float deltaTime) + { + UpdateEscape(deltaTime, canAttackDoors: false); + } + private void CheckCrouching(float deltaTime) { crouchRaycastTimer -= deltaTime; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index 918287729..ef353d352 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -78,7 +78,7 @@ namespace Barotrauma public IndoorsSteeringManager(ISteerable host, bool canOpenDoors, bool canBreakDoors) : base(host) { - pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), indoorsSteering: true); + pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true); pathFinder.GetNodePenalty = GetNodePenalty; this.canOpenDoors = canOpenDoors; @@ -160,26 +160,34 @@ namespace Barotrauma private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisibility = true) { - Vector2 targetDiff = target - currentTarget; - if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null) + bool needsNewPath = currentPath == null || currentPath.Unreachable; + if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f) { - //target in a different sub than where the character is now - //take that into account when calculating if the target has moved - Submarine currentPathSub = currentPath?.CurrentNode?.Submarine; - if (currentPathSub == character.Submarine) { currentPathSub = currentPath?.Nodes.LastOrDefault()?.Submarine; } - if (currentPathSub != character.Submarine && targetDiff.LengthSquared() > 1 && currentPathSub != null) + Vector2 targetDiff = target - currentTarget; + if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null) { - Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition; - targetDiff += subDiff; + //target in a different sub than where the character is now + //take that into account when calculating if the target has moved + Submarine currentPathSub = currentPath?.CurrentNode?.Submarine; + if (currentPathSub == character.Submarine) { currentPathSub = currentPath?.Nodes.LastOrDefault()?.Submarine; } + if (currentPathSub != character.Submarine && targetDiff.LengthSquared() > 1 && currentPathSub != null) + { + Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition; + targetDiff += subDiff; + } + } + if (targetDiff.LengthSquared() > 1) + { + needsNewPath = true; } } - bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || targetDiff.LengthSquared() > 1); //find a new path if one hasn't been found yet or the target is different from the current target if (needsNewPath || findPathTimer < -1.0f) { IsPathDirty = true; if (findPathTimer < 0) { + SkipCurrentPathNodes(); currentTarget = target; Vector2 currentPos = host.SimPosition; if (character != null && character.Submarine == null) @@ -193,7 +201,7 @@ namespace Barotrauma pathFinder.InsideSubmarine = character.Submarine != null; pathFinder.ApplyPenaltyToOutsideNodes = character.PressureProtection <= 0; var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility); - bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.X) <= 0; + bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.X) <= 0; if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable) { // Check if the new path is the same as the old, in which case we just ignore it and continue using the old path (or the progress would reset). @@ -206,7 +214,7 @@ namespace Barotrauma // Use the new path if it has significantly lower cost (don't change the path if it has marginally smaller cost. This reduces navigating backwards due to new path that is calculated from the node just behind us). float t = (float)currentPath.CurrentIndex / (currentPath.Nodes.Count - 1); useNewPath = newPath.Cost < currentPath.Cost * MathHelper.Lerp(0.95f, 0, t); - if (!useNewPath) + if (!useNewPath && character.Submarine != null) { // It's possible that the current path was calculated from a start point that is no longer valid. // Therefore, let's accept also paths with a greater cost than the current, if the current node is much farther than the new start node. @@ -239,6 +247,32 @@ namespace Barotrauma findPathTimer = priority * Rand.Range(1.0f, 1.2f); IsPathDirty = false; return DiffToCurrentNode(); + + void SkipCurrentPathNodes() + { + if (!character.AnimController.InWater || character.Submarine != null) { return; } + if (CurrentPath == null || CurrentPath.Unreachable || CurrentPath.Finished) { return; } + if (CurrentPath.CurrentIndex < 0 || CurrentPath.CurrentIndex >= CurrentPath.Nodes.Count - 1) { return; } + // Check if we could skip ahead to NextNode when the character is swimming and using waypoints outside. + // Do this to optimize the old path before creating and evaluating a new path. + // In general, this is to avoid behavior where: + // a) the character goes back to first reach CurrentNode when the second node would be closer; or + // b) the character moves along the path when they could cut through open space to reduce the total distance. + float pathDistance = Vector2.Distance(character.WorldPosition, CurrentPath.CurrentNode.WorldPosition); + pathDistance += CurrentPath.GetLength(startIndex: CurrentPath.CurrentIndex); + for (int i = CurrentPath.Nodes.Count - 1; i > CurrentPath.CurrentIndex + 1; i--) + { + var waypoint = CurrentPath.Nodes[i]; + float directDistance = Vector2.DistanceSquared(character.WorldPosition, waypoint.WorldPosition); + if (directDistance > (pathDistance * pathDistance) || Submarine.PickBody(host.SimPosition, waypoint.SimPosition, collisionCategory: Physics.CollisionLevel) != null) + { + pathDistance -= CurrentPath.GetLength(startIndex: i - 1, endIndex: i); + continue; + } + CurrentPath.SkipToNode(i); + break; + } + } } } @@ -278,18 +312,15 @@ namespace Barotrauma CheckDoorsInPath(); } Vector2 pos = host.SimPosition; - if (character != null && currentPath.CurrentNode != null) + if (character != null && CurrentPath.CurrentNode?.Submarine != null) { - if (CurrentPath.CurrentNode.Submarine != null) + if (character.Submarine == null) { - if (character.Submarine == null) - { - pos -= CurrentPath.CurrentNode.Submarine.SimPosition; - } - else if (character.Submarine != currentPath.CurrentNode.Submarine) - { - pos -= ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position - character.Submarine.Position); - } + pos -= CurrentPath.CurrentNode.Submarine.SimPosition; + } + else if (character.Submarine != currentPath.CurrentNode.Submarine) + { + pos -= ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position - character.Submarine.Position); } } bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs index 66376cc1d..503ef0efb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs @@ -94,7 +94,7 @@ namespace Barotrauma if (_abandon) { #if DEBUG - if (HumanAIController.debugai && objectiveManager.IsOrder(this) && !objectiveManager.IsCurrentOrder()) + if (HumanAIController.debugai && objectiveManager.IsOrder(this) && !objectiveManager.IsCurrentOrder() && !objectiveManager.IsCurrentOrder()) { throw new Exception("Order abandoned!"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs index e2ca2f781..42950891c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs @@ -83,12 +83,13 @@ namespace Barotrauma if (suitableContainer != null) { bool equip = item.GetComponent() != null || - item.AllowedSlots.None(s => - s == InvSlotType.Card || - s == InvSlotType.Head || - s == InvSlotType.Headset || - s == InvSlotType.InnerClothes || - s == InvSlotType.OuterClothes); + item.AllowedSlots.Any(s => s != InvSlotType.Any) && + item.AllowedSlots.None(s => + s == InvSlotType.Card || + s == InvSlotType.Head || + s == InvSlotType.Headset || + s == InvSlotType.InnerClothes || + s == InvSlotType.OuterClothes); TryAddSubObjective(ref decontainObjective, () => new AIObjectiveDecontainItem(character, item, objectiveManager, targetContainer: suitableContainer.GetComponent()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index e554fb317..187479017 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -252,9 +252,40 @@ namespace Barotrauma { case CombatMode.Offensive: case CombatMode.Arrest: - Engage(); + Engage(deltaTime); break; case CombatMode.Defensive: + if (character.IsOnPlayerTeam && !Enemy.IsPlayer && objectiveManager.IsCurrentOrder()) + { + if ((character.CurrentHull == null || character.CurrentHull == Enemy.CurrentHull) && sqrDistance < 200 * 200) + { + Engage(deltaTime); + } + else + { + // Keep following the goto target + var gotoObjective = objectiveManager.GetOrder(); + if (gotoObjective != null) + { + gotoObjective.ForceAct(deltaTime); + if (!character.AnimController.InWater) + { + HumanAIController.FaceTarget(Enemy); + ForceWalk = true; + HumanAIController.AutoFaceMovement = false; + } + } + else + { + SteeringManager.Reset(); + } + } + } + else + { + Retreat(deltaTime); + } + break; case CombatMode.Retreat: Retreat(deltaTime); break; @@ -671,6 +702,14 @@ namespace Barotrauma { RemoveSubObjective(ref retreatObjective); } + if (character.Submarine == null && sqrDistance < MathUtils.Pow2(maxDistance)) + { + // Swim away + SteeringManager.Reset(); + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.WorldPosition - Enemy.WorldPosition)); + SteeringManager.SteeringAvoid(deltaTime, 5, weight: 2); + return; + } if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted)) { if (findHullTimer > 0) @@ -704,7 +743,7 @@ namespace Barotrauma } } - private void Engage() + private void Engage(float deltaTime) { if (WeaponComponent == null) { @@ -722,6 +761,21 @@ namespace Barotrauma RemoveSubObjective(ref retreatObjective); RemoveSubObjective(ref seekAmmunitionObjective); RemoveSubObjective(ref seekWeaponObjective); + if (character.Submarine == null && WeaponComponent is MeleeWeapon meleeWeapon) + { + if (sqrDistance > MathUtils.Pow2(meleeWeapon.Range)) + { + // Swim towards the target + SteeringManager.Reset(); + SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Enemy), weight: 10); + SteeringManager.SteeringAvoid(deltaTime, 5, weight: 15); + } + else + { + SteeringManager.Reset(); + } + return; + } if (followTargetObjective != null && followTargetObjective.Target != Enemy) { RemoveFollowTarget(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index e582044fb..94dba0146 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -46,7 +46,10 @@ namespace Barotrauma } if (character.CurrentHull == null) { - Priority = (objectiveManager.IsCurrentOrder() || objectiveManager.HasActiveObjective()) && HumanAIController.HasDivingSuit(character) ? 0 : 100; + Priority = (objectiveManager.IsCurrentOrder() || + objectiveManager.IsCurrentOrder() || + objectiveManager.Objectives.Any(o => o.Priority > 0 && o is AIObjectiveCombat)) + && HumanAIController.HasDivingSuit(character) ? 0 : 100; } else { @@ -57,9 +60,11 @@ namespace Barotrauma { Priority = 100; } - else if (objectiveManager.IsCurrentOrder() && character.Submarine != null && !HumanAIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID)) + else if ((objectiveManager.IsCurrentOrder() || objectiveManager.IsCurrentOrder()) && + character.Submarine != null && !HumanAIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID)) { - // Ordered to follow/hold position inside a hostile sub -> ignore find safety unless we need to find a diving gear + // Ordered to follow, hold position, or return back to main sub inside a hostile sub + // -> ignore find safety unless we need to find a diving gear Priority = 0; } Priority = MathHelper.Clamp(Priority, 0, 100); @@ -298,6 +303,7 @@ namespace Barotrauma Hull bestHull = null; float bestValue = 0; + bool bestIsAirlock = false; foreach (Hull hull in Hull.hullList.OrderByDescending(h => EstimateHullSuitability(h))) { if (hull.Submarine == null) { continue; } @@ -306,9 +312,10 @@ namespace Barotrauma if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; } if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; } float hullSafety = 0; - if (character.CurrentHull != null && character.Submarine != null) + bool hullIsAirlock = false; + bool isCharacterInside = character.CurrentHull != null && character.Submarine != null; + if (isCharacterInside) { - // Inside if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; } hullSafety = HumanAIController.GetHullSafety(hull, hull.GetConnectedHulls(true, 1), character); float yDist = Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y); @@ -343,24 +350,16 @@ namespace Barotrauma } else { - // Outside - if (hull.RoomName != null && hull.RoomName.Contains("airlock", StringComparison.OrdinalIgnoreCase)) + // TODO: could also target gaps that get us inside? + if (hull.IsTaggedAirlock()) + { + hullSafety = 100; + hullIsAirlock = true; + } + else if(!bestIsAirlock && hull.LeadsOutside(character)) { hullSafety = 100; } - else - { - // TODO: could also target gaps that get us inside? - foreach (Item item in Item.ItemList) - { - if (item.CurrentHull != hull && item.HasTag("airlock")) - { - hullSafety = 100; - break; - } - } - } - // TODO: could we get a closest door to the outside and target the flowing hull if no airlock is found? // Huge preference for closer targets float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance)); @@ -372,10 +371,11 @@ namespace Barotrauma hullSafety /= 10; } } - if (hullSafety > bestValue) + if (hullSafety > bestValue || (!isCharacterInside && hullIsAirlock && !bestIsAirlock)) { bestHull = hull; bestValue = hullSafety; + bestIsAirlock = hullIsAirlock; } } return bestHull; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index 099c63df4..7c4a2e520 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -159,6 +159,8 @@ namespace Barotrauma } } + public void ForceAct(float deltaTime) => Act(deltaTime); + protected override void Act(float deltaTime) { if (followControlledCharacter) @@ -240,7 +242,7 @@ namespace Barotrauma if (getDivingGearIfNeeded && !character.LockHands) { Character followTarget = Target as Character; - bool needsDivingSuit = targetIsOutside; + bool needsDivingSuit = !isInside || targetIsOutside; bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit); if (mimic) { @@ -444,13 +446,22 @@ namespace Barotrauma } if (SteeringManager == PathSteering) { + Vector2 targetPos = character.GetRelativeSimPosition(Target); Func nodeFilter = null; if (isInside && !AllowGoingOutside) { nodeFilter = n => n.Waypoint.CurrentHull != null; } + else if (!isInside && HumanAIController.UseIndoorSteeringOutside) + { + if (character.Submarine == null && Target.Submarine != null) + { + targetPos += Target.Submarine.SimPosition; + } + nodeFilter = n => n.Waypoint.Tunnel != null; + } - PathSteering.SteeringSeek(character.GetRelativeSimPosition(Target), 1, + PathSteering.SteeringSeek(targetPos, 1, startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null), endNodeFilter, nodeFilter, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 6c868993d..319c68580 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -233,11 +233,7 @@ namespace Barotrauma if (orderObjective == null) { return; } #if DEBUG // Note: don't automatically remove orders here. Removing orders needs to be done via dismissing. - if (orderObjective.IsCompleted) - { - DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag} IS COMPLETED. CURRENTLY ALL ORDERS SHOULD BE LOOPING.", Color.Red); - } - else if (!orderObjective.CanBeCompleted) + if (!orderObjective.CanBeCompleted) { DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag}, CANNOT BE COMPLETED.", Color.Red); } @@ -281,9 +277,9 @@ namespace Barotrauma ForcedOrder?.CalculatePriority(); AIObjective orderWithHighestPriority = null; float highestPriority = 0; - foreach (var currentOrder in CurrentOrders) + for (int i = CurrentOrders.Count - 1; i >= 0; i--) { - var orderObjective = currentOrder.Objective; + var orderObjective = CurrentOrders[i].Objective; if (orderObjective == null) { continue; } orderObjective.CalculatePriority(); if (orderWithHighestPriority == null || orderObjective.Priority > highestPriority) @@ -467,6 +463,11 @@ namespace Barotrauma AllowGoingOutside = character.Submarine == null || (order.TargetSpatialEntity != null && character.Submarine != order.TargetSpatialEntity.Submarine) }; break; + case "return": + newObjective = new AIObjectiveReturn(character, this, priorityModifier: priorityModifier); + newObjective.Abandoned += () => DismissSelf(order, option); + newObjective.Completed += () => DismissSelf(order, option); + break; case "fixleaks": newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier: priorityModifier, prioritizedHull: order.TargetEntity as Hull); break; @@ -586,6 +587,27 @@ namespace Barotrauma return newObjective; } + private void DismissSelf(Order order, string option) + { + var currentOrder = CurrentOrders.FirstOrDefault(oi => oi.MatchesOrder(order, option)); + if (currentOrder.Order == null) + { +#if DEBUG + DebugConsole.ThrowError("Tried to self-dismiss an order, but no matching current order was found"); +#endif + return; + } +#if CLIENT + if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer) + { + GameMain.GameSession?.CrewManager?.SetCharacterOrder(character, Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character); + } +#else + GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, currentOrder.Order?.TargetSpatialEntity, character, character)); +#endif + } + + private bool IsAllowedToWait() { if (!character.IsOnPlayerTeam) { return false; } @@ -606,6 +628,8 @@ namespace Barotrauma public bool IsActiveObjective() where T : AIObjective => GetActiveObjective() is T; public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective(); + public T GetOrder() where T : AIObjective => CurrentOrders.FirstOrDefault(o => o.Objective is T).Objective as T; + /// /// Returns the last active objective of the specific type. /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs new file mode 100644 index 000000000..e9e52e8c1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs @@ -0,0 +1,243 @@ +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + class AIObjectiveReturn : AIObjective + { + public override string Identifier { get; set; } = "return"; + private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective; + private bool usingEscapeBehavior; + public Submarine ReturnTarget { get; } + + public AIObjectiveReturn(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier) + { + ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded); + if (ReturnTarget == null) + { + DebugConsole.ThrowError("Error with a Return objective: no suitable return target found"); + Abandon = true; + } + + Submarine GetReturnTarget(IEnumerable subs) + { + Submarine returnTarget = null; + foreach (var sub in subs) + { + if (sub?.TeamID != character.TeamID) { continue; } + returnTarget = sub; + break; + } + return returnTarget; + } + } + + protected override float GetPriority() + { + if (!Abandon && !IsCompleted && objectiveManager.IsOrder(this)) + { + Priority = objectiveManager.GetOrderPriority(this); + } + else + { + // TODO: Consider if this needs to be addressed + Priority = 0; + } + return Priority; + } + + protected override void Act(float deltaTime) + { + if (ReturnTarget == null) + { + Abandon = true; + return; + } + bool shouldUseEscapeBehavior = false; + if (character.CurrentHull != null) + { + if (character.Submarine == null || !character.Submarine.IsConnectedTo(ReturnTarget)) + { + // Character is on another sub that is not connected to the target sub, use the escape behavior to get them out + shouldUseEscapeBehavior = true; + if (!usingEscapeBehavior) + { + HumanAIController.ResetEscape(); + } + HumanAIController.Escape(deltaTime); + if (HumanAIController.EscapeTarget == null || !HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: false)) + { + Abandon = true; + } + } + else if (character.Submarine != ReturnTarget) + { + // Character is on another sub that is connected to the target sub, create a Go To objective to reach the target sub + if (moveInsideObjective == null) + { + Hull targetHull = null; + foreach (var d in ReturnTarget.ConnectedDockingPorts.Values) + { + if (!d.Docked) { continue; } + if (d.DockingTarget == null) { continue; } + if (d.DockingTarget.Item.Submarine != character.Submarine) { continue; } + targetHull = d.Item.CurrentHull; + break; + } + if (targetHull != null) + { + RemoveSubObjective(ref moveInCaveObjective); + RemoveSubObjective(ref moveOutsideObjective); + // TODO: Check 'repeat' and 'onAbandon' parameters + TryAddSubObjective(ref moveInsideObjective, + constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager), + onCompleted: () => moveInsideObjective = null); + } + else + { + DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveInsideObjective'"); + } + } + } + else + { + // Character is on the target sub, the objective is completed + IsCompleted = true; + } + } + else if (moveInCaveObjective == null && moveOutsideObjective == null) + { + if (HumanAIController.IsInsideCave) + { + WayPoint closestOutsideWaypoint = null; + float closestDistance = float.MaxValue; + foreach (var w in WayPoint.WayPointList) + { + if (w.Tunnel == null) { continue; } + if (w.Tunnel.Type == Level.TunnelType.Cave) { continue; } + if (w.linkedTo.None(l => l is WayPoint linkedWaypoint && linkedWaypoint.Tunnel?.Type == Level.TunnelType.Cave)) { continue; } + float distance = Vector2.DistanceSquared(character.WorldPosition, w.WorldPosition); + if (closestOutsideWaypoint == null || distance < closestDistance) + { + closestOutsideWaypoint = w; + closestDistance = distance; + } + } + if (closestOutsideWaypoint != null) + { + RemoveSubObjective(ref moveInsideObjective); + RemoveSubObjective(ref moveOutsideObjective); + // TODO: Check 'repeat' and 'onAbandon' parameters + TryAddSubObjective(ref moveInCaveObjective, + constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager) + { + endNodeFilter = n => n.Waypoint == closestOutsideWaypoint + }, + onCompleted: () => moveInCaveObjective = null); + } + else + { + DebugConsole.ThrowError("Error with a Return objective: no suitable main or side path node target found for 'moveOutsideObjective'"); + } + } + else + { + Hull targetHull = null; + float targetDistanceSquared = float.MaxValue; + bool targetIsAirlock = false; + foreach (var hull in ReturnTarget.GetHulls(false)) + { + bool hullIsAirlock = hull.IsTaggedAirlock(); + if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character))) + { + float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); + if (targetHull == null || distanceSquared < targetDistanceSquared) + { + targetHull = hull; + targetDistanceSquared = distanceSquared; + targetIsAirlock = hullIsAirlock; + } + } + } + if (targetHull != null) + { + RemoveSubObjective(ref moveInsideObjective); + RemoveSubObjective(ref moveInCaveObjective); + // TODO: Check 'repeat' and 'onAbandon' parameters + TryAddSubObjective(ref moveOutsideObjective, + constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager), + onCompleted: () => moveOutsideObjective = null); + } + else + { + DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'"); + } + } + } + else + { + if (HumanAIController.IsInsideCave) + { + if (moveOutsideObjective != null) + { + RemoveSubObjective(ref moveOutsideObjective); + moveOutsideObjective = null; + } + } + else + { + if (moveInCaveObjective != null) + { + RemoveSubObjective(ref moveInCaveObjective); + moveInCaveObjective = null; + } + } + } + usingEscapeBehavior = shouldUseEscapeBehavior; + } + + protected override bool CheckObjectiveSpecific() + { + if (IsCompleted) + { + return true; + } + if (ReturnTarget == null) + { + Abandon = true; + return false; + } + if (character.Submarine == ReturnTarget) + { + IsCompleted = true; + } + return IsCompleted; + } + + public override void Reset() + { + base.Reset(); + moveInsideObjective = null; + moveInCaveObjective = null; + moveOutsideObjective = null; + usingEscapeBehavior = false; + HumanAIController.ResetEscape(); + } + + protected override void OnAbandon() + { + base.OnAbandon(); + SteeringManager.Reset(); + if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective) + { + string msg = TextManager.Get("dialogcannotreturn", returnNull: true); + if (msg != null) + { + character.Speak(msg, identifier: "dialogcannotreturn", minDurationBetweenSimilar: 5.0f); + } + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 21d8cebe4..8ccdff754 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -23,6 +24,8 @@ namespace Barotrauma public readonly Vector2 Position; public readonly int WayPointID; + public bool blocked; + public override string ToString() { return $"PathNode {WayPointID}"; @@ -86,21 +89,19 @@ namespace Barotrauma public GetNodePenaltyHandler GetNodePenalty; private readonly List nodes; - public readonly bool IndoorsSteering; + private readonly bool isCharacter; public bool InsideSubmarine { get; set; } public bool ApplyPenaltyToOutsideNodes { get; set; } - public PathFinder(List wayPoints, bool indoorsSteering = false) + public PathFinder(List wayPoints, bool isCharacter) { - nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => w.Submarine != null == indoorsSteering), removeOrphans: true); - + nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => (w.Submarine != null == isCharacter) || (isCharacter && w.Tunnel != null)), removeOrphans: true); foreach (WayPoint wp in wayPoints) { wp.OnLinksChanged += WaypointLinksChanged; } - - IndoorsSteering = indoorsSteering; + this.isCharacter = isCharacter; } void WaypointLinksChanged(WayPoint wp) @@ -145,6 +146,8 @@ namespace Barotrauma public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisibility = true) { + UpdateBlockedNodes(); + //sort nodes roughly according to distance sortedNodes.Clear(); foreach (PathNode node in nodes) @@ -152,7 +155,9 @@ namespace Barotrauma node.TempPosition = node.Position; if (hostSub != null) { - Vector2 diff = hostSub.SimPosition - node.Waypoint.Submarine.SimPosition; + Vector2 diff = node.Waypoint.Submarine != null ? + hostSub.SimPosition - node.Waypoint.Submarine.SimPosition : + hostSub.SimPosition - node.Waypoint.SimPosition; node.TempPosition -= diff; } float xDiff = Math.Abs(start.X - node.TempPosition.X); @@ -174,29 +179,34 @@ namespace Barotrauma sortedNodes.Insert(i, node); } + bool IsWaypointVisible(PathNode node, Vector2 rayStart, bool checkVisibility = true) + { + //if searching for a path inside the sub, make sure the waypoint is visible + if (checkVisibility && isCharacter) + { + if (node.Waypoint.isObstructed) { return false; } + var body = Submarine.PickBody(rayStart, node.TempPosition, + collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); + if (body != null) + { + if (body.UserData is Structure s && !s.IsPlatform) { return false; } + if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { return false; } + } + } + return true; + } + //find the most suitable start node, starting from the ones that are the closest PathNode startNode = null; foreach (PathNode node in sortedNodes) { if (startNode == null || node.TempDistance < startNode.TempDistance) { + if (node.blocked) { continue; } if (nodeFilter != null && !nodeFilter(node)) { continue; } if (startNodeFilter != null && !startNodeFilter(node)) { continue; } - //if searching for a path inside the sub, make sure the waypoint is visible - if (IndoorsSteering) - { - if (node.Waypoint.isObstructed) { continue; } - - // Always check the visibility for the start node - var body = Submarine.PickBody( - start, node.TempPosition, null, - Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); - if (body != null) - { - if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; } - if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } - } - } + // Always check the visibility for the start node + if (!IsWaypointVisible(node, start)) { continue; } startNode = node; } } @@ -241,24 +251,11 @@ namespace Barotrauma { if (endNode == null || node.TempDistance < endNode.TempDistance) { + if (node.blocked) { continue; } if (nodeFilter != null && !nodeFilter(node)) { continue; } if (endNodeFilter != null && !endNodeFilter(node)) { continue; } - if (IndoorsSteering) - { - if (node.Waypoint.isObstructed) { continue; } - //if searching for a path inside the sub, make sure the waypoint is visible - if (checkVisibility) - { - // Only check the visibility for the end node when allowed (fix leaks) - var body = Submarine.PickBody(end, node.TempPosition, null, - Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); - if (body != null) - { - if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; } - if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } - } - } - } + // Only check the visibility for the end node when allowed (fix leaks) + if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { continue; } endNode = node; } } @@ -330,7 +327,8 @@ namespace Barotrauma foreach (PathNode node in nodes) { if (node.state != 1) { continue; } - if (IndoorsSteering && node.Waypoint.isObstructed) { continue; } + if (isCharacter && node.Waypoint.isObstructed) { continue; } + if (node.blocked) { continue; } if (filter != null && !filter(node)) { continue; } if (node.F < dist) { @@ -438,6 +436,25 @@ namespace Barotrauma return path; } + + private void UpdateBlockedNodes() + { + if (!isCharacter) { return; } + foreach (var n in nodes) + { + n.blocked = false; + if (n.Waypoint.Submarine != null) { continue; } + if (n.Waypoint.Tunnel?.Type != Level.TunnelType.Cave) { continue; } + foreach (var w in Level.Loaded.ExtraWalls) + { + if (!(w is DestructibleLevelWall d)) { continue; } + if (d.Destroyed) { continue; } + if (!d.IsPointInside(n.Waypoint.Position)) { continue; } + n.blocked = true; + break; + } + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs index 79c8dbefa..24a3feb2b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs @@ -1,4 +1,5 @@ using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; namespace Barotrauma @@ -24,16 +25,47 @@ namespace Barotrauma if (Unreachable) { return float.PositiveInfinity; } if (!totalLength.HasValue) { - totalLength = 0.0f; - for (int i = 0; i < nodes.Count - 1; i++) - { - totalLength += Vector2.Distance(nodes[i].WorldPosition, nodes[i + 1].WorldPosition); - } + CalculateTotalLength(); } return totalLength.Value; } } + public float GetLength(int? startIndex = null, int? endIndex = null) + { + if (Unreachable) { return float.PositiveInfinity; } + startIndex ??= 0; + endIndex ??= Nodes.Count - 1; + if (startIndex == 0 && endIndex == Nodes.Count - 1) + { + return TotalLength; + } + if (!totalLength.HasValue) + { + CalculateTotalLength(); + } + float length = 0.0f; + for (int i = startIndex.Value; i < endIndex.Value; i++) + { + length += nodeDistances[i]; + } + return length; + } + + private void CalculateTotalLength() + { + totalLength = 0.0f; + nodeDistances.Clear(); + for (int i = 0; i < nodes.Count - 1; i++) + { + float distance = Vector2.Distance(nodes[i].WorldPosition, nodes[i + 1].WorldPosition); + totalLength += distance; + nodeDistances.Add(distance); + } + } + + private readonly List nodeDistances = new List(); + public SteeringPath(bool unreachable = false) { nodes = new List(); @@ -107,6 +139,11 @@ namespace Barotrauma currentIndex++; } + public void SkipToNode(int nodeIndex) + { + currentIndex = nodeIndex; + } + public WayPoint CheckProgress(Vector2 simPosition, float minSimDistance = 0.1f) { if (nodes.Count == 0 || currentIndex > nodes.Count - 1) { return null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index f442f7cc1..abd381c65 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -1130,7 +1130,7 @@ namespace Barotrauma { nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction); } - if (ragdollParams == null) + if (ragdollParams == null && prefab.VariantOf == null) { string name = Params.UseHuskAppendage ? nonHuskedSpeciesName : speciesName; ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams(name) : RagdollParams.GetDefaultRagdollParams(name) as RagdollParams; @@ -3078,30 +3078,47 @@ namespace Barotrauma //set the character order only if the character is close enough to hear the message if (!force && orderGiver != null && !CanHearCharacter(orderGiver)) { return; } - if (order.OrderGiver != orderGiver) + if (order != null && order.OrderGiver != orderGiver) { order.OrderGiver = orderGiver; } - // If there's another character operating the same device, make them dismiss themself - if (order != null && order.Category == OrderCategory.Operate && order.TargetEntity != null) + switch (order?.Category) { - foreach (var character in CharacterList) - { - if (character == this) { continue; } - if (character.TeamID != TeamID) { continue; } - if (!(character.AIController is HumanAIController)) { continue; } - if (!HumanAIController.IsActive(character)) { continue; } - foreach (var currentOrder in character.CurrentOrders) + case OrderCategory.Operate when order?.TargetEntity != null: + // If there's another character operating the same device, make them dismiss themself + foreach (var character in CharacterList) + { + if (character == this) { continue; } + if (character.TeamID != TeamID) { continue; } + if (!(character.AIController is HumanAIController)) { continue; } + if (!HumanAIController.IsActive(character)) { continue; } + foreach (var currentOrder in character.CurrentOrders) + { + if (currentOrder.Order == null) { continue; } + if (currentOrder.Order.Category != OrderCategory.Operate) { continue; } + if (currentOrder.Order.Identifier != order.Identifier) { continue; } + if (currentOrder.Order.TargetEntity != order.TargetEntity) { continue; } + character.SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character, speak: speak, force: force); + break; + } + } + break; + case OrderCategory.Movement: + // If there character has another movement order, dismiss that order + OrderInfo? orderToReplace = null; + foreach (var currentOrder in CurrentOrders) { if (currentOrder.Order == null) { continue; } - if (currentOrder.Order.Category != OrderCategory.Operate) { continue; } - if (currentOrder.Order.Identifier != order.Identifier) { continue; } - if (currentOrder.Order.TargetEntity != order.TargetEntity) { continue; } - character.SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character, speak: speak, force: force); + if (currentOrder.Order.Category != OrderCategory.Movement) { continue; } + orderToReplace = currentOrder; break; } - } + if (orderToReplace.HasValue) + { + SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(orderToReplace.Value), orderToReplace.Value.ManualPriority, this, speak: speak, force: force); + } + break; } // Prevent adding duplicate orders @@ -3112,7 +3129,8 @@ namespace Barotrauma if (orderGiver != null) { - orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, this); + var abilityOrderedCharacter = new AbilityCharacter(this); + orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, abilityOrderedCharacter); } if (AIController is HumanAIController humanAI) @@ -3504,11 +3522,12 @@ namespace Barotrauma public void RecordKill(Character target) { + var abilityCharacter = new AbilityCharacter(target); foreach (Character attackerCrewmember in GetFriendlyCrew(this)) { - attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, target); + attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, abilityCharacter); } - CheckTalents(AbilityEffectType.OnKillCharacter, target); + CheckTalents(AbilityEffectType.OnKillCharacter, abilityCharacter); if (!IsOnPlayerTeam) { return; } if (GameMain.Config.KilledCreatures.Any(name => name.Equals(target.SpeciesName, StringComparison.OrdinalIgnoreCase))) { return; } @@ -3604,8 +3623,11 @@ namespace Barotrauma ApplyStatusEffects(ActionType.OnDamaged, 1.0f); hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f); } - - attacker?.CheckTalents(AbilityEffectType.OnAttackResult, attackResult); + if (attacker != null) + { + var abilityAttackResult = new AbilityAttackResult(attackResult); + attacker.CheckTalents(AbilityEffectType.OnAttackResult, abilityAttackResult); + } return attackResult; } @@ -3828,7 +3850,8 @@ namespace Barotrauma causeOfDeathAffliction?.Source ?? LastAttacker, LastDamageSource); OnDeath?.Invoke(this, CauseOfDeath); - CheckTalents(AbilityEffectType.OnDieToCharacter, CauseOfDeath.Killer); + var abilityKiller = new AbilityCharacter(CauseOfDeath.Killer); + CheckTalents(AbilityEffectType.OnDieToCharacter, abilityKiller); if (GameMain.GameSession != null && Screen.Selected == GameMain.GameScreen) { @@ -4347,11 +4370,16 @@ namespace Barotrauma return CharacterList.Where(c => HumanAIController.IsFriendly(character, c, onlySameTeam: true) && !c.IsDead); } - public void CheckTalents(AbilityEffectType abilityEffectType, object abilityData) + public bool HasTalents() + { + return characterTalents.Any(); + } + + public void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject) { foreach (var characterTalent in characterTalents) { - characterTalent.CheckTalent(abilityEffectType, abilityData); + characterTalent.CheckTalent(abilityEffectType, abilityObject); } } @@ -4359,7 +4387,7 @@ namespace Barotrauma { foreach (var characterTalent in characterTalents) { - characterTalent.CheckTalent(abilityEffectType, (object)null); + characterTalent.CheckTalent(abilityEffectType, null); } } @@ -4421,7 +4449,8 @@ namespace Barotrauma //replace by updating the character wearable stat values when equipping or unequipping wearables for (int i = 0; i < Inventory.Capacity; i++) { - if (Inventory.SlotTypes[i] != InvSlotType.Any && Inventory.GetItemAt(i)?.GetComponent() is Wearable wearable) + if (Inventory.SlotTypes[i] != InvSlotType.Any && Inventory.SlotTypes[i] != InvSlotType.LeftHand && Inventory.SlotTypes[i] != InvSlotType.RightHand + && Inventory.GetItemAt(i)?.GetComponent() is Wearable wearable) { if (wearable.WearableStatValues.TryGetValue(statType, out float wearableValue)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index b4b617328..e69408bbd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -220,7 +220,7 @@ namespace Barotrauma public HashSet UnlockedTalents { get; private set; } = new HashSet(); - public int AdditionalTalentPoints { get; private set; } + public int AdditionalTalentPoints { get; set; } private Sprite headSprite; public Sprite HeadSprite @@ -541,6 +541,7 @@ namespace Barotrauma Salary = infoElement.GetAttributeInt("salary", 1000); ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0); UnlockedTalents = new HashSet(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true)); + AdditionalTalentPoints = infoElement.GetAttributeInt("additionaltalentpoints", 0); Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race); Enum.TryParse(infoElement.GetAttributeString("gender", "None"), true, out Gender gender); _speciesName = infoElement.GetAttributeString("speciesname", null); @@ -984,12 +985,14 @@ namespace Barotrauma float newLevel = Job.GetSkillLevel(skillIdentifier); if ((int)newLevel > (int)prevLevel) - { - Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, skillIdentifier); + { + // assume we are getting at least 1 point in skill, since this logic only runs in such cases + float increaseSinceLastSkillPoint = MathHelper.Max(increase, 1f); + var abilitySkillGain = new AbilityValueStringCharacter(increaseSinceLastSkillPoint, skillIdentifier, Character); + Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, abilitySkillGain); foreach (Character character in Character.GetFriendlyCrew(Character)) { - var abilityStringCharacter = new AbilityStringCharacter(skillIdentifier, Character); - character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, abilityStringCharacter); + character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, abilitySkillGain); } } @@ -1138,6 +1141,7 @@ namespace Barotrauma new XAttribute("salary", Salary), new XAttribute("experiencepoints", ExperiencePoints), new XAttribute("unlockedtalents", string.Join(",", UnlockedTalents)), + new XAttribute("additionaltalentpoints", AdditionalTalentPoints), new XAttribute("headspriteid", HeadSpriteId), new XAttribute("hairindex", HairIndex), new XAttribute("beardindex", BeardIndex), diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs index d67a18fde..9cf760566 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs @@ -176,6 +176,21 @@ namespace Barotrauma (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect); } + public float GetAfflictionOverlayMultiplier() + { + //If the overlay's alpha progresses linearly, then don't worry about affliction effects. + if (Prefab.AfflictionOverlayAlphaIsLinear) { return (Strength / Prefab.MaxStrength); } + if (Strength < Prefab.ActivationThreshold) { return 0.0f; } + AfflictionPrefab.Effect currentEffect = GetActiveEffect(); + if (currentEffect == null) { return 0.0f; } + if (currentEffect.MaxAfflictionOverlayAlphaMultiplier - currentEffect.MinAfflictionOverlayAlphaMultiplier < 0.0f) { return 0.0f; } + + return MathHelper.Lerp( + currentEffect.MinAfflictionOverlayAlphaMultiplier, + currentEffect.MaxAfflictionOverlayAlphaMultiplier, + (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + } + public float GetScreenBlurStrength() { if (Strength < Prefab.ActivationThreshold) { return 0.0f; } @@ -344,6 +359,8 @@ namespace Barotrauma private readonly List targets = new List(); public void ApplyStatusEffect(ActionType type, StatusEffect statusEffect, float deltaTime, CharacterHealth characterHealth, Limb targetLimb) { + if (type == ActionType.OnDamaged && !statusEffect.HasRequiredAfflictions(characterHealth.Character.LastDamage)) { return; } + statusEffect.SetUser(Source); if (statusEffect.HasTargetType(StatusEffect.TargetType.Character)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 72c3dfae7..7007563e0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -21,6 +21,8 @@ namespace Barotrauma private Character character; + private bool stun = true; + private readonly List huskInfection = new List(); [Serialize(0f, true), Editable] @@ -34,6 +36,7 @@ namespace Barotrauma float threshold = _strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1; float max = Math.Max(threshold, previousValue); _strength = Math.Clamp(value, 0, max); + stun = GameMain.GameSession?.IsRunning ?? true; if (previousValue > 0.0f && value <= 0.0f) { DeactivateHusk(); @@ -60,6 +63,8 @@ namespace Barotrauma private float TransitionThreshold => (Prefab as AfflictionPrefabHusk)?.TransitionThreshold ?? Prefab.MaxStrength * 0.75f; + private float TransformThresholdOnDeath => (Prefab as AfflictionPrefabHusk)?.TransformThresholdOnDeath ?? ActiveThreshold; + public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) { } public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime) @@ -91,7 +96,7 @@ namespace Barotrauma } else if (Strength < TransitionThreshold) { - if (State != InfectionState.Active) + if (State != InfectionState.Active && stun) { character.SetStun(Rand.Range(2, 4)); } @@ -167,7 +172,7 @@ namespace Barotrauma private void CharacterDead(Character character, CauseOfDeath causeOfDeath) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } - if (Strength < ActiveThreshold || character.Removed) + if (Strength < TransformThresholdOnDeath || character.Removed) { UnsubscribeFromDeathEvent(); return; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index 5028df129..809ccf081 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -101,6 +101,7 @@ namespace Barotrauma DormantThreshold = element.GetAttributeFloat("dormantthreshold", MaxStrength * 0.5f); ActiveThreshold = element.GetAttributeFloat("activethreshold", MaxStrength * 0.75f); TransitionThreshold = element.GetAttributeFloat("transitionthreshold", MaxStrength); + TransformThresholdOnDeath = element.GetAttributeFloat("transformthresholdondeath", ActiveThreshold); } // Use any of these to define which limb the appendage is attached to. @@ -110,6 +111,7 @@ namespace Barotrauma public readonly LimbType AttachLimbType; public float ActiveThreshold, DormantThreshold, TransitionThreshold; + public float TransformThresholdOnDeath; public readonly string HuskedSpeciesName; public readonly string[] TargetSpecies; @@ -182,6 +184,12 @@ namespace Barotrauma [Serialize(0.0f, false)] public float ScreenEffectFluctuationFrequency { get; private set; } + [Serialize(1.0f, false)] + public float MinAfflictionOverlayAlphaMultiplier { get; private set; } + + [Serialize(1.0f, false)] + public float MaxAfflictionOverlayAlphaMultiplier { get; private set; } + [Serialize(1.0f, false)] public float MinBuffMultiplier { get; private set; } @@ -358,6 +366,9 @@ namespace Barotrauma public readonly Sprite Icon; public readonly Color[] IconColors; + public readonly Sprite AfflictionOverlay; + public readonly bool AfflictionOverlayAlphaIsLinear; + private readonly List effects = new List(); private readonly List periodicEffects = new List(); @@ -645,6 +656,7 @@ namespace Barotrauma SelfCauseOfDeathDescription = TextManager.Get("AfflictionCauseOfDeathSelf." + translationId, true) ?? element.GetAttributeString("selfcauseofdeathdescription", ""); IconColors = element.GetAttributeColorArray("iconcolors", null); + AfflictionOverlayAlphaIsLinear = element.GetAttributeBool("afflictionoverlayalphaislinear", false); AchievementOnRemoved = element.GetAttributeString("achievementonremoved", ""); foreach (XElement subElement in element.Elements()) @@ -654,6 +666,9 @@ namespace Barotrauma case "icon": Icon = new Sprite(subElement); break; + case "afflictionoverlay": + AfflictionOverlay = new Sprite(subElement); + break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index a195d5620..8f882e2f7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -6,6 +6,7 @@ using System.Xml.Linq; using Barotrauma.Networking; using Barotrauma.Extensions; using System.Globalization; +using Barotrauma.Abilities; namespace Barotrauma { @@ -483,9 +484,12 @@ namespace Barotrauma { var matchingAffliction = matchingAfflictions[i]; - // kind of bad to create a tuple every time, but I can't think of another way to easily do this - var afflictionReduction = (matchingAffliction, reduceAmount); - Character.CheckTalents(AbilityEffectType.OnReduceAffliction, afflictionReduction); + // this logic runs very often, so culling unnecessary object creation and talent checking with this method + if (Character.HasTalents()) + { + var afflictionReduction = new AbilityValueAffliction(reduceAmount, matchingAffliction); + Character.CheckTalents(AbilityEffectType.OnReduceAffliction, afflictionReduction); + } if (matchingAffliction.Strength < reduceAmount) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index 9afd742bb..bf6179d52 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -11,6 +11,7 @@ using System.Xml.Linq; using Barotrauma.Networking; using LimbParams = Barotrauma.RagdollParams.LimbParams; using JointParams = Barotrauma.RagdollParams.JointParams; +using Barotrauma.Abilities; namespace Barotrauma { @@ -741,7 +742,11 @@ namespace Barotrauma { newAffliction.SetStrength(affliction.NonClampedStrength); } - attacker?.CheckTalents(AbilityEffectType.OnAddDamageAffliction, newAffliction); + if (attacker != null) + { + var abilityAffliction = new AbilityAffliction(newAffliction); + attacker.CheckTalents(AbilityEffectType.OnAddDamageAffliction, abilityAffliction); + } if (applyAffliction) { afflictionsCopy.Add(newAffliction); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs index 80b4be072..4bdddab03 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs @@ -20,7 +20,7 @@ namespace Barotrauma.Abilities character = characterTalent.Character; invert = conditionElement.GetAttributeBool("invert", false); } - public abstract bool MatchesCondition(object abilityData); + public abstract bool MatchesCondition(AbilityObject abilityObject); public abstract bool MatchesCondition(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs index 3288b0b7a..ee5fedac0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs @@ -31,9 +31,9 @@ namespace Barotrauma.Abilities } } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is AbilityAttackData attackData) + if (abilityObject is AbilityAttackData attackData) { Item item = attackData?.SourceAttack?.SourceItem; @@ -71,7 +71,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityConditionError(abilityData, typeof(AbilityAttackData)); + LogAbilityConditionError(abilityObject, typeof(AbilityAttackData)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs index ac9eb34df..a5f321518 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs @@ -15,9 +15,9 @@ namespace Barotrauma.Abilities afflictions = conditionElement.GetAttributeStringArray("afflictions", new string[0], convertToLowerInvariant: true); } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is AttackResult attackResult) + if ((abilityObject as IAbilityAttackResult)?.AttackResult is AttackResult attackResult) { if (!IsViableTarget(targetTypes, attackResult.HitLimb?.character)) { return false; } @@ -30,7 +30,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityConditionError(abilityData, typeof(AbilityAttackData)); + LogAbilityConditionError(abilityObject, typeof(IAbilityAttackResult)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs index 1254f8058..2670ea21e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs @@ -12,9 +12,9 @@ namespace Barotrauma.Abilities targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true)); } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is Character character) + if ((abilityObject as IAbilityCharacter)?.Character is Character character) { if (!IsViableTarget(targetTypes, character)) { return false; } @@ -22,7 +22,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityConditionError(abilityData, typeof(Character)); + LogAbilityConditionError(abilityObject, typeof(IAbilityCharacter)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs index 5135b0924..fe3608df4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs @@ -16,21 +16,21 @@ namespace Barotrauma.Abilities /// public AbilityConditionData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } - protected void LogAbilityConditionError(T abilityData, Type expectedData) + protected void LogAbilityConditionError(AbilityObject abilityObject, Type expectedData) { - DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityData}"); + DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityObject}"); } - protected abstract bool MatchesConditionSpecific(object abilityData); + protected abstract bool MatchesConditionSpecific(AbilityObject abilityObject); public override bool MatchesCondition() { DebugConsole.ThrowError("Used data-reliant ability condition in a state-based ability! This is not allowed."); return false; } - public override bool MatchesCondition(object abilityData) + public override bool MatchesCondition(AbilityObject abilityObject) { - if (abilityData is null) { return invert; } - return invert ? !MatchesConditionSpecific(abilityData) : MatchesConditionSpecific(abilityData); + if (abilityObject is null) { return invert; } + return invert ? !MatchesConditionSpecific(abilityObject) : MatchesConditionSpecific(abilityObject); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionEvasiveManeuvers.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionEvasiveManeuvers.cs index 44336e5b8..2e1204fba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionEvasiveManeuvers.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionEvasiveManeuvers.cs @@ -6,15 +6,15 @@ namespace Barotrauma.Abilities { public AbilityConditionEvasiveManeuvers(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is Submarine submarine) + if ((abilityObject as IAbilitySubmarine)?.Submarine is Submarine submarine && (abilityObject as IAbilityCharacter)?.Character is Character attackingCharacter) { - return submarine.TeamID == character.TeamID && character.Submarine == submarine; + return submarine.TeamID == character.TeamID && character.Submarine == submarine && attackingCharacter.TeamID != character.TeamID; } else { - LogAbilityConditionError(abilityData, typeof(Submarine)); + LogAbilityConditionError(abilityObject, typeof(IAbilitySubmarine)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs index 4e6b530cd..ff6cba362 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs @@ -15,33 +15,33 @@ namespace Barotrauma.Abilities tags = conditionElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - ItemPrefab item = null; - if (abilityData is Item tempItem) + ItemPrefab itemPrefab = null; + if ((abilityObject as IAbilityItemPrefab)?.ItemPrefab is ItemPrefab abilityItemPrefab) { - item = tempItem.Prefab; + itemPrefab = abilityItemPrefab; } - else if (abilityData is IAbilityItemPrefab abilityItemPrefab) + else if ((abilityObject as IAbilityItem)?.Item is Item abilityItem) { - item = abilityItemPrefab.ItemPrefab; + itemPrefab = abilityItem.Prefab; } - if (item != null) + if (itemPrefab != null) { if (!string.IsNullOrEmpty(identifier)) { - if (item.Identifier != identifier) + if (itemPrefab.Identifier != identifier) { return false; } } - return tags.Any(t => item.Tags.Any(p => t == p)); + return tags.Any(t => itemPrefab.Tags.Any(p => t == p)); } else { - LogAbilityConditionError(abilityData, typeof(Item)); + LogAbilityConditionError(abilityObject, typeof(IAbilityItemPrefab)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs index 888217b9f..1068da08b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs @@ -13,19 +13,19 @@ namespace Barotrauma.Abilities identifier = conditionElement.GetAttributeString("identifier", ""); } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is IAbilityAffliction abilityAffliction) + if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction) { - if (allowedTypes.Find(c => c == abilityAffliction.Affliction.Prefab.AfflictionType) == null) { return false; } + if (allowedTypes.Find(c => c == affliction.Prefab.AfflictionType) == null) { return false; } - if (!string.IsNullOrEmpty(identifier) && abilityAffliction.Affliction.Prefab.Identifier != identifier) { return false; } + if (!string.IsNullOrEmpty(identifier) && affliction.Prefab.Identifier != identifier) { return false; } return true; } else { - LogAbilityConditionError(abilityData, typeof(IAbilityAffliction)); + LogAbilityConditionError(abilityObject, typeof(IAbilityAffliction)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs index 2156ccae1..e1ca65e72 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs @@ -4,17 +4,18 @@ namespace Barotrauma.Abilities { class AbilityConditionScavenger : AbilityConditionData { + public AbilityConditionScavenger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is Item item) + if ((abilityObject as IAbilityItem)?.Item is Item item) { - return item.Submarine != character.Submarine; + return item.Submarine == null || item.Submarine.TeamID != character.Info.TeamID; } else { - LogAbilityConditionError(abilityData, typeof(Item)); + LogAbilityConditionError(abilityObject, typeof(IAbilityItem)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScrounger.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScrounger.cs new file mode 100644 index 000000000..9fc5b00cb --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScrounger.cs @@ -0,0 +1,23 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionScrounger : AbilityConditionData + { + + public AbilityConditionScrounger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityItem)?.Item is Item item) + { + return item.Submarine?.Info?.IsWreck ?? false; + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityItem)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs index 37fd19923..5c368df8f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs @@ -16,15 +16,15 @@ namespace Barotrauma.Abilities return this.skillIdentifier == skillIdentifier; } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if ((abilityData as string ?? (abilityData as IAbilityString)?.String) is string skillIdentifier) + if ((abilityObject as IAbilityString)?.String is string skillIdentifier) { return MatchesConditionSpecific(skillIdentifier); } else { - LogAbilityConditionError(abilityData, typeof(string)); + LogAbilityConditionError(abilityObject, typeof(IAbilityString)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs index de8bccced..ad7007fd6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs @@ -12,7 +12,7 @@ namespace Barotrauma.Abilities return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific(); } - public override bool MatchesCondition(object abilityData) + public override bool MatchesCondition(AbilityObject abilityObject) { return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs index 29cab2f45..f7f0ffed4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs @@ -22,15 +22,15 @@ namespace Barotrauma.Abilities } } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is IAbilityMission abilityMission) + if ((abilityObject as IAbilityMission)?.Mission is Mission mission) { - return abilityMission.Mission.Prefab.Type == missionType; + return mission.Prefab.Type == missionType; } else { - LogAbilityConditionError(abilityData, typeof(IAbilityMission)); + LogAbilityConditionError(abilityObject, typeof(IAbilityMission)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs index 46478b7b7..5a169edf3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs @@ -5,6 +5,11 @@ public ItemPrefab ItemPrefab { get; set; } } + interface IAbilityItem + { + public Item Item { get; set; } + } + interface IAbilityValue { public float Value { get; set; } @@ -29,4 +34,14 @@ { public Affliction Affliction { get; set; } } + + interface IAbilityAttackResult + { + public AttackResult AttackResult { get; set; } + } + + interface IAbilitySubmarine + { + public Submarine Submarine { get; set; } + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs index 9247ad159..292be7b94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs @@ -2,8 +2,30 @@ namespace Barotrauma.Abilities { + class AbilityObject + { + // kept as blank for now, as we are using a composition and only using this object to enforce parameter types + } - class AbilityValue : IAbilityValue + class AbilityCharacter : AbilityObject, IAbilityCharacter + { + public AbilityCharacter(Character character) + { + Character = character; + } + public Character Character { get; set; } + } + + class AbilityItem : AbilityObject, IAbilityItem + { + public AbilityItem(Item item) + { + Item = item; + } + public Item Item { get; set; } + } + + class AbilityValue : AbilityObject, IAbilityValue { public AbilityValue(float value) { @@ -12,7 +34,16 @@ namespace Barotrauma.Abilities public float Value { get; set; } } - class AbilityValueItem : IAbilityValue, IAbilityItemPrefab + class AbilityAffliction : AbilityObject, IAbilityAffliction + { + public AbilityAffliction(Affliction affliction) + { + Affliction = affliction; + } + public Affliction Affliction { get; set; } + } + + class AbilityValueItem : AbilityObject, IAbilityValue, IAbilityItemPrefab { public AbilityValueItem(float value, ItemPrefab itemPrefab) { @@ -23,7 +54,7 @@ namespace Barotrauma.Abilities public ItemPrefab ItemPrefab { get; set; } } - class AbilityValueString : IAbilityValue, IAbilityString + class AbilityValueString : AbilityObject, IAbilityValue, IAbilityString { public AbilityValueString(float value, string abilityString) { @@ -34,7 +65,20 @@ namespace Barotrauma.Abilities public string String { get; set; } } - class AbilityStringCharacter : IAbilityCharacter, IAbilityString + class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString + { + public AbilityValueStringCharacter(float value, string abilityString, Character character) + { + Value = value; + String = abilityString; + Character = character; + } + public Character Character { get; set; } + public float Value { get; set; } + public string String { get; set; } + } + + class AbilityStringCharacter : AbilityObject, IAbilityCharacter, IAbilityString { public AbilityStringCharacter(string abilityString, Character character) { @@ -45,7 +89,7 @@ namespace Barotrauma.Abilities public string String { get; set; } } - class AbilityValueAffliction : IAbilityValue, IAbilityAffliction + class AbilityValueAffliction : AbilityObject, IAbilityValue, IAbilityAffliction { public AbilityValueAffliction(float value, Affliction affliction) { @@ -56,7 +100,7 @@ namespace Barotrauma.Abilities public Affliction Affliction { get; set; } } - class AbilityValueMission : IAbilityValue, IAbilityMission + class AbilityValueMission : AbilityObject, IAbilityValue, IAbilityMission { public AbilityValueMission(float value, Mission mission) { @@ -67,7 +111,8 @@ namespace Barotrauma.Abilities public Mission Mission { get; set; } } - class AbilityAttackData : IAbilityCharacter + // this is an exception class that should only be passed in this form, so classes that use it should cast into it directly + class AbilityAttackData : AbilityObject, IAbilityCharacter { public float DamageMultiplier { get; set; } = 1f; public float AddedPenetration { get; set; } = 0f; @@ -82,4 +127,26 @@ namespace Barotrauma.Abilities Character = character; } } + + class AbilityAttackResult : AbilityObject, IAbilityAttackResult + { + public AttackResult AttackResult { get; set; } + + public AbilityAttackResult(AttackResult attackResult) + { + AttackResult = attackResult; + } + } + + class AbilityCharacterSubmarine : AbilityObject, IAbilityCharacter, IAbilitySubmarine + { + public AbilityCharacterSubmarine(Character character, Submarine submarine) + { + Character = character; + Submarine = submarine; + } + public Character Character { get; set; } + public Submarine Submarine { get; set; } + } + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs index 9b4414901..ceaa14ee8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs @@ -65,15 +65,15 @@ namespace Barotrauma.Abilities DebugConsole.ThrowError($"Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups."); } - public void ApplyAbilityEffect(object abilityData) + public void ApplyAbilityEffect(AbilityObject abilityObject) { - if (abilityData is null) + if (abilityObject is null) { ApplyEffect(); } else { - ApplyEffect(abilityData); + ApplyEffect(abilityObject); } } @@ -82,12 +82,12 @@ namespace Barotrauma.Abilities DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect"); } - protected virtual void ApplyEffect(object abilityData) + protected virtual void ApplyEffect(AbilityObject abilityObject) { DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect"); } - protected void LogAbilityDataMismatch() + protected void LogabilityObjectMismatch() { DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type."); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs index 773992306..6dbc6450e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs @@ -34,32 +34,33 @@ namespace Barotrauma.Abilities { targets.Clear(); targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets)); + statusEffect.SetUser(Character); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets); } - else if (statusEffect.HasTargetType(StatusEffect.TargetType.This)) + else if (statusEffect.HasTargetType(StatusEffect.TargetType.Character)) + { + statusEffect.SetUser(Character); + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); + } + else { statusEffect.SetUser(Character); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, Character); } - else - { - statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); - } } } - protected override void ApplyEffect() { ApplyEffectSpecific(Character); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter) { ApplyEffectSpecific(selectedCharacter); } - else if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter) + else if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) { ApplyEffectSpecific(targetCharacter); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs index 1f346accd..efca07622 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs @@ -9,9 +9,9 @@ namespace Barotrauma.Abilities { } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if ((abilityData as AbilityAttackData)?.Attacker is Character attacker) + if ((abilityObject as AbilityAttackData)?.Attacker is Character attacker) { ApplyEffectSpecific(attacker); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs new file mode 100644 index 000000000..5de3fb85f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs @@ -0,0 +1,27 @@ +using Microsoft.Xna.Framework; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGainSimultaneousSkill : CharacterAbility + { + private string skillIdentifier; + + public CharacterAbilityGainSimultaneousSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant(); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityValue)?.Value is float skillIncrease) + { + Character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, Character.Position + Vector2.UnitY * 175.0f); + } + else + { + LogabilityObjectMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs index 4ac33d7c7..a10d132c5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs @@ -27,9 +27,9 @@ namespace Barotrauma.Abilities targetCharacter.GiveMoney((int)(multiplier * amount)); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter) + if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) { ApplyEffectSpecific(targetCharacter); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index c8147397c..55da7e1cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -12,6 +12,8 @@ namespace Barotrauma.Abilities private readonly bool targetAllies; private readonly bool removeOnDeath; private readonly bool removeAfterRound; + private readonly bool giveOnAddingFirstTime; + //private readonly float maximumValue; public override bool AppliesEffectOnIntervalUpdate => true; @@ -25,10 +27,19 @@ namespace Barotrauma.Abilities targetAllies = abilityElement.GetAttributeBool("targetallies", false); removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false); + giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", false); //maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue); } - protected override void ApplyEffect(object abilityData) + public override void InitializeAbility(bool addingFirstTime) + { + if (giveOnAddingFirstTime && addingFirstTime) + { + ApplyEffectSpecific(); + } + } + + protected override void ApplyEffect(AbilityObject abilityObject) { ApplyEffectSpecific(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs new file mode 100644 index 000000000..8ba1c9ef9 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs @@ -0,0 +1,23 @@ +using Barotrauma.Extensions; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveTalentPoints : CharacterAbility + { + private readonly int amount; + + public CharacterAbilityGiveTalentPoints(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + amount = abilityElement.GetAttributeInt("amount", 0); + } + + public override void InitializeAbility(bool addingFirstTime) + { + if (addingFirstTime && Character.Info != null) + { + Character.Info.AdditionalTalentPoints += amount; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs index 44caf675a..830d4c9ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs @@ -21,9 +21,9 @@ namespace Barotrauma.Abilities ApplyEffectSpecific(Character); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is Character character) + if ((abilityObject as IAbilityCharacter)?.Character is Character character) { ApplyEffectSpecific(character); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs index 84aa32f90..5d073a068 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs @@ -15,9 +15,9 @@ namespace Barotrauma.Abilities addedMultiplier = abilityElement.GetAttributeFloat("addedmultiplier", 0f); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is Affliction affliction) + if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction) { foreach (string afflictionIdentifier in afflictionIdentifiers) { @@ -29,7 +29,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityDataMismatch(); + LogabilityObjectMismatch(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs index 1d32acc85..2e742bb3f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs @@ -22,9 +22,9 @@ namespace Barotrauma.Abilities implode = abilityElement.GetAttributeBool("implode", false); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is AbilityAttackData attackData) + if (abilityObject is AbilityAttackData attackData) { if (attackData.Afflictions == null) { @@ -46,7 +46,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityDataMismatch(); + LogabilityObjectMismatch(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs index 828714266..affb06085 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs @@ -12,15 +12,15 @@ namespace Barotrauma.Abilities addedAmountMultiplier = abilityElement.GetAttributeFloat("addedamountmultiplier", 0f); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is (Affliction affliction, float reduceAmount)) + if (abilityObject is AbilityValueAffliction afflictionReduceAmount) { - affliction.Strength -= addedAmountMultiplier * reduceAmount; + afflictionReduceAmount.Affliction.Strength -= addedAmountMultiplier * afflictionReduceAmount.Value; } else { - LogAbilityDataMismatch(); + LogabilityObjectMismatch(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs index b4e15f0b3..8c12ea122 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs @@ -13,9 +13,9 @@ namespace Barotrauma.Abilities multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is IAbilityValue abilityValue) + if (abilityObject is IAbilityValue abilityValue) { abilityValue.Value += addedValue; abilityValue.Value *= multiplyValue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs index 06706568c..0957982df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs @@ -11,7 +11,7 @@ namespace Barotrauma.Abilities { statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant(); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { ApplyEffectSpecific(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs index 15772796c..317d12487 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs @@ -21,7 +21,7 @@ namespace Barotrauma.Abilities ApplyEffectSpecific(); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { ApplyEffectSpecific(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs new file mode 100644 index 000000000..d092e972c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilitySpawnItemsToContainer : CharacterAbility + { + // currently used only for spawning items to containers + + private readonly List statusEffects; + private readonly List openedContainers = new List(); + private readonly float randomChance; + + public CharacterAbilitySpawnItemsToContainer(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); + randomChance = abilityElement.GetAttributeFloat("randomchance", 1f); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityItem)?.Item is Item item) + { + if (openedContainers.Contains(item)) { return; } + openedContainers.Add(item); + if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; } + + foreach (var statusEffect in statusEffects) + { + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, item, item); + } + } + else + { + LogabilityObjectMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs index 9b498a085..cabb8a78c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs @@ -18,9 +18,9 @@ namespace Barotrauma.Abilities tags = abilityElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is AbilityAttackData attackData) + if (abilityObject is AbilityAttackData attackData) { float totalAddedDamageMultiplier = 0f; foreach (Item item in Character.Inventory.AllItems) @@ -34,7 +34,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityDataMismatch(); + LogabilityObjectMismatch(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs index 8196229ff..b02fbe021 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs @@ -10,11 +10,11 @@ namespace Barotrauma.Abilities { } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is AbilityStringCharacter abilityStringCharacter && abilityStringCharacter.Character != Character) + if ((abilityObject as IAbilityString)?.String is string skillIdentifier && (abilityObject as IAbilityCharacter)?.Character is Character character) { - Character.Info?.IncreaseSkillLevel(abilityStringCharacter.String, 1.0f, abilityStringCharacter.Character.Position + Vector2.UnitY * 175.0f); + Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs index fde6e081e..417b93972 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs @@ -12,9 +12,9 @@ namespace Barotrauma.Abilities vitalityPercentage = abilityElement.GetAttributeFloat("vitalitypercentage", 0f); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is Character character) + if ((abilityObject as IAbilityCharacter)?.Character is Character character) { Character.GiveMoney((int)(vitalityPercentage * character.MaxVitality)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs index 0017b6d98..b22d35c46 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs @@ -11,9 +11,9 @@ namespace Barotrauma.Abilities { } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is string skillIdentifier) + if ((abilityObject as IAbilityString)?.String is string skillIdentifier) { if (skillIdentifier != lastSkillIdentifier) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs index cb82641f5..574d9d7b1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs @@ -8,19 +8,26 @@ namespace Barotrauma.Abilities { class CharacterAbilityRegenerateLoot : CharacterAbility { + // separate random chance used for the ability itself to prevent the player + // from opening/reopening a container until it spawns loot + private readonly float randomChance; + // not maintained through death, so it's possible for players to respawn and re-loot chests // seems like a minor issue for now - List openedContainers = new List(); + private readonly List openedContainers = new List(); public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { + randomChance = abilityElement.GetAttributeFloat("randomchance", 1f); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is Item item && !openedContainers.Contains(item)) + if ((abilityObject as IAbilityItem)?.Item is Item item) { + if (openedContainers.Contains(item)) { return; } openedContainers.Add(item); + if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; } if (item.GetComponent() is ItemContainer itemContainer) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs index d54950e74..2ae4b6256 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs @@ -16,9 +16,9 @@ namespace Barotrauma.Abilities statusEffectsRemove = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsremove")); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is Character targetCharacter) + if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) { if (targetCharacter == Character) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs index 184c5e8a9..55629172f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs @@ -10,25 +10,25 @@ namespace Barotrauma.Abilities { public CharacterAbilityGroupEffect(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) { } - public void CheckAbilityGroup(object abilityData) + public void CheckAbilityGroup(AbilityObject abilityObject) { if (!IsActive) { return; } - if (IsApplicable(abilityData)) + if (IsApplicable(abilityObject)) { foreach (var characterAbility in characterAbilities) { if (characterAbility.IsViable()) { - characterAbility.ApplyAbilityEffect(abilityData); + characterAbility.ApplyAbilityEffect(abilityObject); } } } } - private bool IsApplicable(object abilityData) + private bool IsApplicable(AbilityObject abilityObject) { if (timesTriggered >= maxTriggerCount) { return false; } - return abilityConditions.All(c => c.MatchesCondition(abilityData)); + return abilityConditions.All(c => c.MatchesCondition(abilityObject)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs index e9ac4347a..5d8a1587b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs @@ -60,13 +60,13 @@ namespace Barotrauma } } - public void CheckTalent(AbilityEffectType abilityEffectType, object abilityData) + public void CheckTalent(AbilityEffectType abilityEffectType, AbilityObject abilityObject) { if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityGroups)) { foreach (var characterAbilityGroup in characterAbilityGroups) { - characterAbilityGroup.CheckAbilityGroup(abilityData); + characterAbilityGroup.CheckAbilityGroup(abilityObject); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs index ed67c56be..dfcd2b32d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs @@ -420,7 +420,9 @@ namespace Barotrauma default: try { - XDocument.Load(file.Path); + using FileStream stream = File.Open(file.Path, System.IO.FileMode.Open, System.IO.FileAccess.Read); + using var reader = XMLExtensions.CreateReader(stream); + XDocument.Load(reader); } catch (Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index 346ceb31e..be70e66b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -57,6 +57,7 @@ OnGainMissionMoney, OnItemDeconstructed, OnItemDeconstructedMaterial, + OnStopTinkering, AfterSubmarineAttacked, } @@ -89,6 +90,7 @@ // Utility RepairSpeed, DeconstructorSpeedMultiplier, + TinkeringDuration, // Misc ReputationGainMultiplier, MissionMoneyGainMultiplier, @@ -108,6 +110,8 @@ IgnoredByEnemyAI, MoveNormallyWhileDragging, CanTinker, + CanTinkerFabricatorsAndDeconstructors, + TinkeringPowersDevices, GainSkillPastMaximum, RetainExperienceForNewCharacter } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs index 97bd587e8..fea12aa2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs @@ -122,7 +122,7 @@ namespace Barotrauma { npcOrItem = npc; npc.CampaignInteractionType = CampaignMode.InteractionType.Examine; - npc.RequireConsciousnessForCustomInteract = false; + npc.RequireConsciousnessForCustomInteract = DisableIfTargetIncapacitated; #if CLIENT npc.SetCustomInteract( (speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } }, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index c9b03e99c..49b16f9a3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -103,7 +103,7 @@ namespace Barotrauma selectedEvents.Clear(); activeEvents.Clear(); - pathFinder = new PathFinder(WayPoint.WayPointList, indoorsSteering: false); + pathFinder = new PathFinder(WayPoint.WayPointList, false); totalPathLength = 0.0f; if (level != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index dcc9d3279..023e1e860 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -140,6 +140,12 @@ namespace Barotrauma public override int GetReward(Submarine sub) { + // If we are not at the location of the mission, skip the calculation of the reward + if (GameMain.GameSession?.StartLocation != Locations[0]) + { + return calculatedReward; + } + bool missionsChanged = false; if (GameMain.GameSession?.StartLocation?.SelectedMissions != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/ForbiddenWordFilter.cs b/Barotrauma/BarotraumaShared/SharedSource/ForbiddenWordFilter.cs index 593283d23..bcdbde9f6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ForbiddenWordFilter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ForbiddenWordFilter.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.IO; +using Barotrauma.IO; using System.Linq; namespace Barotrauma @@ -16,7 +16,7 @@ namespace Barotrauma { forbiddenWords = File.ReadAllLines(fileListPath).Select(s => s.ToLowerInvariant()).ToHashSet(); } - catch (IOException e) + catch (System.IO.IOException e) { DebugConsole.ThrowError($"Failed to load the list of forbidden words from {fileListPath}.", e); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 4f941e847..b1e2c79e8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -1,12 +1,11 @@ using Barotrauma.Items.Components; +using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; -using Barotrauma.Networking; -using Barotrauma.Extensions; namespace Barotrauma { @@ -17,11 +16,17 @@ namespace Barotrauma // Anything that uses this field I wasn't sure if actually needed the proper campaign settings to be passed down public static CampaignSettings Unsure = Empty; public bool RadiationEnabled { get; set; } - public int MaxMissionCount { get; set; } + public int AddedMissionCount { get; set; } public int TotalMaxMissionCount => MaxMissionCount + AddedMissionCount; + private int maxMissionCount; + public int MaxMissionCount + { + get { return maxMissionCount; } + set { maxMissionCount = MathHelper.Clamp(value, MinMissionCountLimit, MaxMissionCountLimit); } + } public const int DefaultMaxMissionCount = 2; public const int MaxMissionCountLimit = 10; @@ -29,16 +34,18 @@ namespace Barotrauma public CampaignSettings(IReadMessage inc) { + maxMissionCount = DefaultMaxMissionCount; RadiationEnabled = inc.ReadBoolean(); - MaxMissionCount = inc.ReadInt32(); AddedMissionCount = inc.ReadInt32(); + MaxMissionCount = inc.ReadInt32(); } public CampaignSettings(XElement element) { + maxMissionCount = DefaultMaxMissionCount; RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLowerInvariant(), true); - MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount); AddedMissionCount = element.GetAttributeInt(nameof(AddedMissionCount).ToLowerInvariant(), 0); + MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount); } public void Serialize(IWriteMessage msg) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 8b6cbeb36..cb791155d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -659,9 +659,9 @@ namespace Barotrauma public static IEnumerable GetSessionCrewCharacters() { #if SERVER - return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c.Info != null); + return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null); #else - return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c.Info != null); + return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null); #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 8b65d6f30..a4061293b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -291,11 +291,25 @@ namespace Barotrauma { currentSlot = i; if (allowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) - inSuitableSlot = true; + { + if ((SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) && !allowedSlots.Contains(SlotTypes[i])) + { + //allowed slot = InvSlotType.RightHand | InvSlotType.LeftHand + // -> make sure the item is in both hand slots + inSuitableSlot = IsInLimbSlot(item, InvSlotType.RightHand) && IsInLimbSlot(item, InvSlotType.LeftHand); + } + else + { + inSuitableSlot = true; + } + } else if (!allowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) + { inWrongSlot = true; + } } } + //all good if (inSuitableSlot && !inWrongSlot) { return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs index 99927ca74..bf6875202 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs @@ -67,7 +67,7 @@ namespace Barotrauma.Items.Components private bool isBroken; - public bool CanBeTraversed => (IsOpen || IsBroken) && !IsJammed && !IsStuck; + public bool CanBeTraversed => (IsOpen || IsBroken) && !IsJammed && !IsStuck && !Impassable; public bool IsBroken { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs index e9aa55e2b..60b304478 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs @@ -116,7 +116,11 @@ namespace Barotrauma.Items.Components #if SERVER item.CreateServerEvent(this); #endif - } + } + foreach (Item containedItem in item.ContainedItems) + { + containedItem.GetComponent()?.Equip(character); + } } public override void Update(float deltaTime, Camera cam) @@ -124,12 +128,16 @@ namespace Barotrauma.Items.Components base.Update(deltaTime, cam); if (targetCharacter != null) { + var rootContainer = item.GetRootContainer(); if (!targetCharacter.HasEquippedItem(item) && - (item.Container == null || !targetCharacter.HasEquippedItem(item.Container) || !(item.Container.GetComponent()?.AutoInject ?? false))) + (rootContainer == null || !targetCharacter.HasEquippedItem(rootContainer) || !targetCharacter.Inventory.IsInLimbSlot(rootContainer, InvSlotType.HealthInterface))) { item.ApplyStatusEffects(ActionType.OnSevered, 1.0f, targetCharacter); - var currentEffect = tainted ? selectedTaintedEffect : selectedEffect; - targetCharacter.CharacterHealth.ReduceAffliction(null, currentEffect.Identifier, currentEffect.MaxStrength); + targetCharacter.CharacterHealth.ReduceAffliction(null, selectedEffect.Identifier, selectedEffect.MaxStrength); + if (tainted) + { + targetCharacter.CharacterHealth.ReduceAffliction(null, selectedTaintedEffect.Identifier, selectedTaintedEffect.MaxStrength); + } targetCharacter = null; IsActive = false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index 14d626d16..73d89fe0f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.Abilities; +using Barotrauma.Networking; using FarseerPhysics; using FarseerPhysics.Collision; using FarseerPhysics.Dynamics; @@ -158,10 +159,14 @@ namespace Barotrauma.Items.Components if (currentChargeTime < MaxChargeTime) { return false; } IsActive = true; - ReloadTimer = reload / (1 + character.GetStatValue(StatTypes.RangedAttackSpeed)); + ReloadTimer = reload / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f); currentChargeTime = 0f; - character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, item); + if (character != null) + { + var abilityItem = new AbilityItem(item); + character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, abilityItem); + } if (item.AiTarget != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 76093809d..ba479089d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -6,6 +6,7 @@ using System.Xml.Linq; using Barotrauma.Extensions; using FarseerPhysics; using System.Collections.Immutable; +using Barotrauma.Abilities; namespace Barotrauma.Items.Components { @@ -114,6 +115,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(true, false)] + public bool AllowSwappingContainedItems + { + get; + set; + } + [Serialize(false, false, description: "If set to true, interacting with this item will make the character interact with the contained item(s), automatically picking them up if they can be picked up.")] public bool AutoInteractWithContained { @@ -414,7 +422,8 @@ namespace Barotrauma.Items.Components } } } - character.CheckTalents(AbilityEffectType.OnOpenItemContainer, item); + var abilityItem = new AbilityItem(item); + character.CheckTalents(AbilityEffectType.OnOpenItemContainer, abilityItem); return base.Select(character); } @@ -588,6 +597,7 @@ namespace Barotrauma.Items.Components public override void OnItemLoaded() { + Inventory.AllowSwappingContainedItems = AllowSwappingContainedItems; containableItemIdentifiers = slotRestrictions.SelectMany(s => s.ContainableItems?.SelectMany(ri => ri.Identifiers) ?? Enumerable.Empty()).ToImmutableHashSet(); if (item.Submarine == null || !item.Submarine.Loading) { @@ -616,7 +626,21 @@ namespace Barotrauma.Items.Components } itemIds = null; } - SpawnAlwaysContainedItems(); + + //outpost and ruins are loaded in multiple stages (each module is loaded separately) + //spawning items at this point during the generation will cause ID overlaps with the entities in the modules loaded afterwards + //so let's not spawn them at this point, but in the 1st Update() + if (item.Submarine?.Info != null && (item.Submarine.Info.IsOutpost || item.Submarine.Info.IsRuin)) + { + if (SpawnWithId.Length > 0) + { + IsActive = true; + } + } + else + { + SpawnAlwaysContainedItems(); + } } private void SpawnAlwaysContainedItems() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index 3fe8d3b74..18996185f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -106,7 +106,7 @@ namespace Barotrauma.Items.Components if ((Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false) || !inputContainer.Inventory.AllItems.Contains(targetItem)) { continue; } var validDeconstructItems = targetItem.Prefab.DeconstructItems.FindAll(it => (it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))) && - (it.RequiredOtherItem.Length == 0 || it.RequiredOtherItem.Any(r => items.Any(it => it.HasTag(r) || it.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))))); + (it.RequiredOtherItem.Length == 0 || it.RequiredOtherItem.Any(r => items.Any(it => it != targetItem && (it.HasTag(r) || it.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase)))))); ProcessItem(targetItem, items, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any()); } @@ -148,6 +148,12 @@ namespace Barotrauma.Items.Components // In multiplayer, the server handles the deconstruction into new items if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + if (user != null && !user.Removed) + { + var abilityTargetItem = new AbilityItem(targetItem); + user.CheckTalents(AbilityEffectType.OnItemDeconstructed, abilityTargetItem); + } + if (targetItem.Prefab.RandomDeconstructionOutput) { int amount = targetItem.Prefab.RandomDeconstructionOutputAmount; @@ -169,8 +175,6 @@ namespace Barotrauma.Items.Components commonness.RemoveAt(removeIndex); } - user.CheckTalents(AbilityEffectType.OnItemDeconstructed, targetItem); - foreach (DeconstructItem deconstructProduct in products) { CreateDeconstructProduct(deconstructProduct, inputItems); @@ -225,10 +229,15 @@ namespace Barotrauma.Items.Components } } } - var itemsCreated = new AbilityValue(1f); - user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, (targetItem.Prefab, itemsCreated)); - int amount = (int)itemsCreated.Value; + int amount = 1; + + if (user != null && !user.Removed) + { + var itemsCreated = new AbilityValueItem(1f, targetItem.Prefab); + user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated); + amount = (int)itemsCreated.Value; + } for (int i = 0; i < amount; i++) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs index d766f09fa..e5849b584 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs @@ -113,37 +113,35 @@ namespace Barotrauma.Items.Components Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, 0.1f); if (Math.Abs(Force) > 1.0f) { + float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f); + float currForce = force * voltageFactor; + float condition = item.Condition / item.MaxCondition; + // Broken engine makes more noise. + float noise = Math.Abs(currForce) * MathHelper.Lerp(1.5f, 1f, condition); + UpdateAITargets(noise); //arbitrary multiplier that was added to changes in submarine mass without having to readjust all engines float forceMultiplier = 0.1f; if (User != null) { forceMultiplier *= MathHelper.Lerp(0.5f, 2.0f, (float)Math.Sqrt(User.GetSkillLevel("helm") / 100)); } - - float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f); - Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f); - - if (item.GetComponent()?.IsTinkering ?? false) + currForce *= maxForce * forceMultiplier; + if (item.GetComponent() is Repairable repairable && repairable.IsTinkering) { currForce *= 2.5f; } //less effective when in a bad condition - currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition); + currForce *= MathHelper.Lerp(0.5f, 2.0f, condition); if (item.Submarine.FlippedX) { currForce *= -1; } - item.Submarine.ApplyForce(currForce); + Vector2 forceVector = new Vector2(currForce, 0); + item.Submarine.ApplyForce(forceVector); UpdatePropellerDamage(deltaTime); - float maxChangeSpeed = 0.5f; - float modifier = 2; - float noise = MathUtils.NearlyEqual(0.0f, maxForce) ? 0.0f : currForce.Length() * forceMultiplier * modifier / maxForce; - float min = Math.Max(1 - maxChangeSpeed, 0); - float max = 1 + maxChangeSpeed; - UpdateAITargets(Math.Clamp(noise, min, max), deltaTime); #if CLIENT particleTimer -= deltaTime; if (particleTimer <= 0.0f) { - Vector2 particleVel = -currForce.ClampLength(5000.0f) / 5.0f; + Vector2 particleVel = -forceVector.ClampLength(5000.0f) / 5.0f; GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos * item.Scale, particleVel * Rand.Range(0.9f, 1.1f), 0.0f, item.CurrentHull); @@ -153,14 +151,14 @@ namespace Barotrauma.Items.Components } } - private void UpdateAITargets(float increaseSpeed, float deltaTime) + private void UpdateAITargets(float noise) { if (item.AiTarget != null) { - item.AiTarget.IncreaseSoundRange(deltaTime, increaseSpeed); + item.AiTarget.SoundRange = MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, noise / 100); if (item.CurrentHull != null && item.CurrentHull.AiTarget != null) { - // It's possible that some othe item increases the hull's soundrange more than the engine. + // It's possible that some other item increases the hull's soundrange more than the engine. item.CurrentHull.AiTarget.SoundRange = Math.Max(item.CurrentHull.AiTarget.SoundRange, item.AiTarget.SoundRange); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 1ea32a0f3..d47c21d23 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -327,6 +327,7 @@ namespace Barotrauma.Items.Components int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health); var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem); + if (user != null) { foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID)) @@ -369,10 +370,11 @@ namespace Barotrauma.Items.Components float addedSkill = skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f); var addedSkillValue = new AbilityValueString(0f, skill.Identifier); user.CheckTalents(AbilityEffectType.OnItemFabricationSkillGain, addedSkillValue); + addedSkill += addedSkillValue.Value; user.Info.IncreaseSkillLevel( skill.Identifier, - addedSkill + addedSkillValue.Value, + addedSkill, user.Position + Vector2.UnitY * 150.0f); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs index 4231489a5..7b00dd94e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs @@ -106,7 +106,7 @@ namespace Barotrauma.Items.Components currFlow = flowPercentage / 100.0f * maxFlow * powerFactor; - if (item.GetComponent()?.IsTinkering ?? false) + if (item.GetComponent() is Repairable repairable && repairable.IsTinkering) { currFlow *= 2.5f; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index e8866afaf..bac4219d8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs @@ -367,23 +367,19 @@ namespace Barotrauma.Items.Components item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime; } } - - if (item.CurrentHull != null) - { - var aiTarget = item.CurrentHull.AiTarget; - if (aiTarget != null && MaxPowerOutput > 0) - { - float range = Math.Abs(currPowerConsumption) / MaxPowerOutput; - float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range); - aiTarget.SoundRange = Math.Max(aiTarget.SoundRange, noise); - } - } - if (item.AiTarget != null && MaxPowerOutput > 0) { var aiTarget = item.AiTarget; float range = Math.Abs(currPowerConsumption) / MaxPowerOutput; aiTarget.SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range); + if (item.CurrentHull != null) + { + var hullAITarget = item.CurrentHull.AiTarget; + if (hullAITarget != null) + { + hullAITarget.SoundRange = Math.Max(hullAITarget.SoundRange, aiTarget.SoundRange); + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 37f5833fc..65a531190 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -85,10 +85,10 @@ namespace Barotrauma.Items.Components } private float skillRequirementMultiplier; - + [Serialize(1.0f, true)] - public float SkillRequirementMultiplier - { + public float SkillRequirementMultiplier + { get { return skillRequirementMultiplier; } set { @@ -100,7 +100,7 @@ namespace Barotrauma.Items.Components RecreateGUI(); } #endif - } + } } public bool IsTinkering { get; private set; } = false; @@ -113,6 +113,8 @@ namespace Barotrauma.Items.Components public Character CurrentFixer { get; private set; } private Item currentRepairItem; + private float tinkeringDuration; + public enum FixActions : int { None = 0, @@ -135,13 +137,13 @@ namespace Barotrauma.Items.Components canBeSelected = true; this.item = item; - header = + header = TextManager.Get(element.GetAttributeString("header", ""), returnNull: true) ?? TextManager.Get(item.Prefab.ConfigElement.GetAttributeString("header", ""), returnNull: true) ?? element.GetAttributeString("name", ""); //backwards compatibility - var repairThresholdAttribute = + var repairThresholdAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("showrepairuithreshold", StringComparison.OrdinalIgnoreCase)) ?? element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("airepairthreshold", StringComparison.OrdinalIgnoreCase)); if (repairThresholdAttribute != null) @@ -197,7 +199,7 @@ namespace Barotrauma.Items.Components return ((average + 100.0f) / 2.0f) / 100.0f; } - + public bool StartRepairing(Character character, FixActions action) { if (character == null || character.IsDead || action == FixActions.None) @@ -227,6 +229,18 @@ namespace Barotrauma.Items.Components CurrentFixer = character; currentRepairItem = bestRepairItem; CurrentFixerAction = action; + if (action == FixActions.Tinker) + { + if (character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors) && item.GetComponent() != null || item.GetComponent() != null) + { + // fabricators and deconstructors can be tinkered indefinitely (more or less) + tinkeringDuration = float.MaxValue; + } + else + { + tinkeringDuration = CurrentFixer.GetStatValue(StatTypes.TinkeringDuration); + } + } return true; Item GetBestRepairItem(Character character) @@ -254,13 +268,17 @@ namespace Barotrauma.Items.Components ic.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, character); } } + if (CurrentFixerAction == FixActions.Tinker) + { + CurrentFixer.CheckTalents(AbilityEffectType.OnStopTinkering); + } CurrentFixer.AnimController.Anim = AnimController.Animation.None; CurrentFixer = null; currentRepairItem = null; currentFixerAction = FixActions.None; #if CLIENT repairSoundChannel?.FadeOutAndDispose(); - repairSoundChannel = null; + repairSoundChannel = null; #endif return true; } @@ -290,6 +308,8 @@ namespace Barotrauma.Items.Components UpdateProjSpecific(deltaTime); IsTinkering = false; + item.SendSignal($"{(int) item.ConditionPercentage}", "condition_out"); + if (CurrentFixer == null) { if (deteriorateAlwaysResetTimer > 0.0f) @@ -339,8 +359,9 @@ namespace Barotrauma.Items.Components if (currentFixerAction == FixActions.Tinker) { + tinkeringDuration -= deltaTime; // not great to interject it here, should be less reliant on returning - if (!CanTinker(CurrentFixer)) + if (!CanTinker(CurrentFixer) || tinkeringDuration <= 0f) { StopRepairing(CurrentFixer); } @@ -396,7 +417,7 @@ namespace Barotrauma.Items.Components CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete); } deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay); - wasBroken = false; + wasBroken = false; StopRepairing(CurrentFixer); } } @@ -439,9 +460,27 @@ namespace Barotrauma.Items.Components } } - private bool CanTinker(Character character) + private bool IsTinkerable(Character character) { if (!character.HasAbilityFlag(AbilityFlags.CanTinker)) { return false; } + if (item.GetComponent() != null) { return true; } + if (item.GetComponent() != null) { return true; } + if (item.GetComponent() != null) { return true; } + if (!character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors)) { return false; } + if (item.GetComponent() != null) { return true; } + if (item.GetComponent() != null) { return true; } + return false; + } + + private Affliction GetTinkerExhaustion(Character character) + { + return character.CharacterHealth.GetAffliction("tinkerexhaustion"); + } + + private bool CanTinker(Character character) + { + if (!IsTinkerable(character)) { return false; } + if (GetTinkerExhaustion(character) is Affliction tinkerExhaustion && tinkerExhaustion.Strength <= tinkerExhaustion.Prefab.MaxStrength) { return false; } return true; } @@ -469,7 +508,7 @@ namespace Barotrauma.Items.Components } else if (ic is PowerTransfer pt) { - //power transfer items (junction boxes, relays) don't deteriorate if they're no carrying any power + //power transfer items (junction boxes, relays) don't deteriorate if they're no carrying any power if (Math.Abs(pt.CurrPowerConsumption) > 0.1f) { return true; } } else if (ic is Engine engine) @@ -530,7 +569,7 @@ namespace Barotrauma.Items.Components public override void ReceiveSignal(Signal signal, Connection connection) { //do nothing - //Repairables should always stay active, so we don't want to use the default behavior + //Repairables should always stay active, so we don't want to use the default behavior //where set_active/set_state signals can disable the component } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs index a836fab59..f6b290bc3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs @@ -233,6 +233,8 @@ namespace Barotrauma.Items.Components } UpdateOnActiveEffects(deltaTime); + if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; } + #if CLIENT Light.ParentSub = item.Submarine; #endif @@ -293,8 +295,6 @@ namespace Barotrauma.Items.Components } SetLightSourceState(true, lightBrightness); - - if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; } } public override void UpdateBroken(float deltaTime, Camera cam) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index ecb5d1cf4..fbf1e9961 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -742,10 +742,10 @@ namespace Barotrauma.Items.Components Projectile projectileComponent = projectile.GetComponent(); if (projectileComponent != null) { - projectileComponent.Attacker = user; + projectileComponent.Attacker = projectileComponent.User = user; if (isTinkering) { - projectileComponent.Attack.DamageMultiplier *= 1.25f; + projectileComponent.Attack.DamageMultiplier = 1.25f; } projectileComponent.Use(); projectile.GetComponent()?.Attach(item, projectile); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index f422df2e8..ccb06ad4a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -258,6 +258,8 @@ namespace Barotrauma get { return capacity; } } + public bool AllowSwappingContainedItems = true; + public Inventory(Entity owner, int capacity, int slotsPerRow = 5) { this.capacity = capacity; @@ -623,6 +625,8 @@ namespace Barotrauma protected bool TrySwapping(int index, Item item, Character user, bool createNetworkEvent, bool swapWholeStack) { if (item?.ParentInventory == null || !slots[index].Any()) { return false; } + if (slots[index].Items.Any(it => !it.IsInteractable(user))) { return false; } + if (!AllowSwappingContainedItems) { return false; } //swap to InvSlotType.Any if possible Inventory otherInventory = item.ParentInventory; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 14fb5af13..6e6cfb9a0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -230,8 +230,7 @@ namespace Barotrauma public bool IsInteractable(Character character) { if (character != null && character.IsOnPlayerTeam) - { - + { return IsPlayerTeamInteractable; } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs index 905b91ff3..2efb1e919 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs @@ -41,6 +41,11 @@ namespace Barotrauma get { return type; } } + /// + /// Index of the slot the target must be in when targeting a Contained item + /// + public int TargetSlot = -1; + public string JoinedIdentifiers { get { return string.Join(",", Identifiers); } @@ -147,7 +152,9 @@ namespace Barotrauma foreach (Item contained in parentItem.ContainedItems) { + if (TargetSlot > -1 && parentItem.OwnInventory.FindIndex(contained) != TargetSlot) { continue; } if ((!ExcludeBroken || contained.Condition > 0.0f) && MatchesItem(contained)) { return true; } + if (CheckContained(contained)) { return true; } } return false; @@ -271,6 +278,8 @@ namespace Barotrauma ri.IsOptional = element.GetAttributeBool("optional", false); ri.IgnoreInEditor = element.GetAttributeBool("ignoreineditor", false); ri.MatchOnEmpty = element.GetAttributeBool("matchonempty", false); + ri.TargetSlot = element.GetAttributeInt("targetslot", -1); + return ri; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index d99fa611d..ba3774838 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -471,9 +471,8 @@ namespace Barotrauma { aiTarget = new AITarget(this) { - MinSightRange = 2000, + MinSightRange = 1000, MaxSightRange = 5000, - MaxSoundRange = 5000, SoundRange = 0 }; } @@ -787,7 +786,7 @@ namespace Barotrauma if (aiTarget != null) { - aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : Submarine.Velocity.Length() / 2 * aiTarget.MaxSightRange; + aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : MathHelper.Lerp(aiTarget.MinSightRange, aiTarget.MaxSightRange, Submarine.Velocity.Length() / 10); aiTarget.SoundRange -= deltaTime * 1000.0f; } @@ -1244,6 +1243,44 @@ namespace Barotrauma return "RoomName.Sub" + roomPos.ToString(); } + /// + /// Is this hull or any of the items inside it tagged as "airlock"? + /// + public bool IsTaggedAirlock() + { + if (RoomName != null && RoomName.Contains("airlock", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else + { + foreach (Item item in Item.ItemList) + { + if (item.CurrentHull != this && item.HasTag("airlock")) + { + return true; + } + } + } + return false; + } + + /// + /// Does this hull have any doors leading outside? + /// + /// Used to check if this character has access to the door leading outside + public bool LeadsOutside(Character character) + { + foreach (var gap in ConnectedGaps) + { + if (gap.ConnectedDoor == null) { continue; } + if (gap.IsRoomToRoom) { continue; } + if (!gap.ConnectedDoor.CanBeTraversed && (character == null || !gap.ConnectedDoor.HasAccess(character))) { continue; } + return true; + } + return false; + } + #region BackgroundSections private void CreateBackgroundSections() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index ad5279bc9..d2e77976e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -415,14 +415,13 @@ namespace Barotrauma //connect clone wires to the clone items and refresh links between doors and gaps for (int i = 0; i < clones.Count; i++) { - var cloneItem = clones[i] as Item; - if (cloneItem == null) { continue; } + if (!(clones[i] is Item cloneItem)) { continue; } var door = cloneItem.GetComponent(); door?.RefreshLinkedGap(); var cloneWire = cloneItem.GetComponent(); - if (cloneWire == null) continue; + if (cloneWire == null) { continue; } var originalWire = ((Item)entitiesToClone[i]).GetComponent(); @@ -430,10 +429,23 @@ namespace Barotrauma for (int n = 0; n < 2; n++) { - if (originalWire.Connections[n] == null) { continue; } + if (originalWire.Connections[n] == null) + { + var disconnectedFrom = entitiesToClone.Find(e => e is Item item && (item.GetComponent()?.DisconnectedWires.Contains(originalWire) ?? false)); + if (disconnectedFrom == null) { continue; } + + int disconnectedFromIndex = entitiesToClone.IndexOf(disconnectedFrom); + var disconnectedFromClone = (clones[disconnectedFromIndex] as Item)?.GetComponent(); + if (disconnectedFromClone == null) { continue; } + + disconnectedFromClone.DisconnectedWires.Add(cloneWire); + if (cloneWire.Item.body != null) { cloneWire.Item.body.Enabled = false; } + cloneWire.IsActive = false; + continue; + } var connectedItem = originalWire.Connections[n].Item; - if (connectedItem == null) continue; + if (connectedItem == null) { continue; } //index of the item the wire is connected to int itemIndex = entitiesToClone.IndexOf(connectedItem); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index f099563c6..d142fadbc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using Barotrauma.Abilities; #if CLIENT using Microsoft.Xna.Framework.Graphics; #endif @@ -437,8 +438,8 @@ namespace Barotrauma { aiTarget = new AITarget(this) { - MinSightRange = 2000, - MaxSightRange = 5000, + MinSightRange = 1000, + MaxSightRange = 4000, MaxSoundRange = 0 }; } @@ -956,11 +957,12 @@ namespace Barotrauma } #endif - if (Submarine != null && damageAmount > 0) + if (Submarine != null && damageAmount > 0 && attacker != null) { + var abilityAttackerSubmarine = new AbilityCharacterSubmarine(attacker, Submarine); foreach (Character character in Character.CharacterList) { - character.CheckTalents(AbilityEffectType.AfterSubmarineAttacked, Submarine); + character.CheckTalents(AbilityEffectType.AfterSubmarineAttacked, abilityAttackerSubmarine); } } @@ -1462,7 +1464,7 @@ namespace Barotrauma { if (aiTarget != null) { - aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : Submarine.Velocity.Length() / 2 * aiTarget.MaxSightRange; + aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : MathHelper.Lerp(aiTarget.MinSightRange, aiTarget.MaxSightRange, Submarine.Velocity.Length() / 10); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs index 8e718636a..e97b6fa06 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs @@ -96,6 +96,9 @@ namespace Barotrauma public OutpostModuleInfo OutpostModuleInfo { get; set; } public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule; + + //TODO: replace when the ruin branch is merged + public bool IsRuin => false; public bool IsWreck => Type == SubmarineType.Wreck; public bool IsBeacon => Type == SubmarineType.BeaconStation; public bool IsPlayer => Type == SubmarineType.Player; @@ -743,7 +746,10 @@ namespace Barotrauma try { stream.Position = 0; - doc = XDocument.Load(stream); //ToolBox.TryLoadXml(file); + using (var reader = XMLExtensions.CreateReader(stream)) + { + doc = XDocument.Load(reader); + } stream.Close(); stream.Dispose(); } @@ -760,9 +766,10 @@ namespace Barotrauma try { ToolBox.IsProperFilenameCase(file); - doc = XDocument.Load(file, LoadOptions.SetBaseUri); + using var stream = File.Open(file, System.IO.FileMode.Open, System.IO.FileAccess.Read); + using var reader = XMLExtensions.CreateReader(stream); + doc = XDocument.Load(reader); } - catch (Exception e) { exception = e; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index c68215f53..4d335dc73 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -892,8 +892,15 @@ namespace Barotrauma.Networking get; set; } - // we do not serialize this value because it relies on a default setting - public int MaxMissionCount { get; set; } = CampaignSettings.DefaultMaxMissionCount; + + private int maxMissionCount = CampaignSettings.DefaultMaxMissionCount; + + [Serialize(CampaignSettings.DefaultMaxMissionCount, true)] + public int MaxMissionCount + { + get { return maxMissionCount; } + set { maxMissionCount = MathHelper.Clamp(value, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); } + } public void SetPassword(string password) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs index 8cd8dae34..f8ce3190b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs @@ -73,19 +73,7 @@ namespace Barotrauma public int GetMaxMissionCount() { -#if CLIENT - // this seems rather silly, but it matches the radiation enabled check structurally. is this right? - if (maxMissionCountText != null && Int32.TryParse(maxMissionCountText.Text, out int result)) - { - return result; - } - else - { - return 0; - } -#elif SERVER - return GameMain.Server.ServerSettings.MaxMissionCount; -#endif + return GameMain.NetworkMember?.ServerSettings?.MaxMissionCount ?? 0; } public void ToggleTraitorsEnabled(int dir) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index 00c4f0517..0e594bbe7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -1,5 +1,4 @@ using System; -using Barotrauma.IO; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -7,20 +6,54 @@ using System.Text; using System.Xml; using System.Xml.Linq; using Microsoft.Xna.Framework; +using File = Barotrauma.IO.File; +using FileStream = Barotrauma.IO.FileStream; namespace Barotrauma { public static class XMLExtensions { - public static string ParseContentPathFromUri(this XObject element) => Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri); + public static string ParseContentPathFromUri(this XObject element) => System.IO.Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri); + public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Prohibit, + XmlResolver = null + }; + + public static XmlReader CreateReader(System.IO.Stream stream) + => XmlReader.Create(stream, ReaderSettings); + + public static XDocument TryLoadXml(System.IO.Stream stream) + { + XDocument doc; + try + { + using XmlReader reader = CreateReader(stream); + doc = XDocument.Load(reader); + } + catch (Exception e) + { + DebugConsole.ThrowError($"Couldn't load xml document from stream!", e); + return null; + } + if (doc?.Root == null) + { + DebugConsole.ThrowError("XML could not be loaded from stream: Document or the root element is invalid!"); + return null; + } + return doc; + } + public static XDocument TryLoadXml(string filePath) { XDocument doc; try { ToolBox.IsProperFilenameCase(filePath); - doc = XDocument.Load(filePath, LoadOptions.SetBaseUri); + using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); + using XmlReader reader = CreateReader(stream); + doc = XDocument.Load(reader); } catch (Exception e) { @@ -45,7 +78,9 @@ namespace Barotrauma { try { - doc = XDocument.Load(filePath, LoadOptions.SetBaseUri); + using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); + using XmlReader reader = CreateReader(stream); + doc = XDocument.Load(reader); } catch { diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index d9ae32ee1..d5a517d5a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -137,6 +137,7 @@ namespace Barotrauma public readonly ItemPrefab ItemPrefab; public readonly SpawnPositionType SpawnPosition; + public readonly bool SpawnIfInventoryFull; public readonly float Speed; public readonly float Rotation; public readonly int Count; @@ -173,6 +174,7 @@ namespace Barotrauma } } + SpawnIfInventoryFull = element.GetAttributeBool("spawnifinventoryfull", false); Speed = element.GetAttributeFloat("speed", 0.0f); Rotation = element.GetAttributeFloat("rotation", 0.0f); @@ -312,6 +314,8 @@ namespace Barotrauma private set; } + private bool modifyAfflictionsByMaxVitality; + public IEnumerable SpawnCharacters { get { return spawnCharacters; } @@ -373,6 +377,7 @@ namespace Barotrauma ReduceAffliction = new List<(string affliction, float amount)>(); giveExperiences = new List(); giveSkills = new List<(string, float)>(); + modifyAfflictionsByMaxVitality = element.GetAttributeBool("multiplyafflictionsbymaxvitality", false); tags = new HashSet(element.GetAttributeString("tags", "").Split(',')); OnlyInside = element.GetAttributeBool("onlyinside", false); @@ -695,7 +700,7 @@ namespace Barotrauma { if (requiredAfflictions == null) { return true; } if (attackResult.Afflictions == null) { return false; } - if (attackResult.Afflictions.None(a => requiredAfflictions.Any(a2 => a.Strength >= a2.strength && a.Identifier == a2.affliction || a.Prefab.AfflictionType == a2.affliction))) + if (attackResult.Afflictions.None(a => requiredAfflictions.Any(a2 => a.Strength >= a2.strength && (a.Identifier == a2.affliction || a.Prefab.AfflictionType == a2.affliction)))) { return false; } @@ -1151,7 +1156,7 @@ namespace Barotrauma if (target is Character character) { if (character.Removed) { continue; } - newAffliction = GetMultipliedAffliction(affliction, entity, character, deltaTime); + newAffliction = GetMultipliedAffliction(affliction, entity, character, deltaTime, modifyAfflictionsByMaxVitality); character.LastDamageSource = entity; foreach (Limb limb in character.AnimController.Limbs) { @@ -1169,7 +1174,7 @@ namespace Barotrauma { if (limb.IsSevered) { continue; } if (limb.character.Removed || limb.Removed) { continue; } - newAffliction = GetMultipliedAffliction(affliction, entity, limb.character, deltaTime); + newAffliction = GetMultipliedAffliction(affliction, entity, limb.character, deltaTime, modifyAfflictionsByMaxVitality); AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue); limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true); RegisterTreatmentResults(entity, limb, affliction, result); @@ -1417,9 +1422,9 @@ namespace Barotrauma { inventory = item?.GetComponent()?.Inventory; } - if (inventory != null && inventory.CanBePut(chosenItemSpawnInfo.ItemPrefab)) + if (inventory != null && (inventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull)) { - Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: false); + Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull); } } break; @@ -1439,9 +1444,9 @@ namespace Barotrauma foreach (Item item in thisInventory.AllItems) { Inventory containedInventory = item.GetComponent()?.Inventory; - if (containedInventory != null && containedInventory.CanBePut(chosenItemSpawnInfo.ItemPrefab)) + if (containedInventory != null && (containedInventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull)) { - Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: false); + Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull); } break; } @@ -1543,14 +1548,14 @@ namespace Barotrauma if (target is Character character) { if (character.Removed) { continue; } - newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, character, deltaTime); + newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, character, deltaTime, element.Parent.modifyAfflictionsByMaxVitality); var result = character.AddDamage(character.WorldPosition, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attacker: element.User); element.Parent.RegisterTreatmentResults(element.Entity, result.HitLimb, affliction, result); } else if (target is Limb limb) { if (limb.character.Removed || limb.Removed) { continue; } - newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, limb.character, deltaTime); + newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, limb.character, deltaTime, element.Parent.modifyAfflictionsByMaxVitality); var result = limb.character.DamageLimb(limb.WorldPosition, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User); element.Parent.RegisterTreatmentResults(element.Entity, limb, affliction, result); } @@ -1614,9 +1619,14 @@ namespace Barotrauma return multiplier; } - private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter, float deltaTime) + private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter, float deltaTime, bool modifyByMaxVitality) { float afflictionMultiplier = GetAfflictionMultiplier(entity, targetCharacter, deltaTime); + if (modifyByMaxVitality) + { + afflictionMultiplier *= targetCharacter.MaxVitality / 100f; + } + if (!MathUtils.NearlyEqual(afflictionMultiplier, 1.0f)) { return affliction.CreateMultiplied(afflictionMultiplier); diff --git a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs index dd975c774..f05045e14 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs @@ -49,7 +49,7 @@ namespace Barotrauma Reactor reactor = item.GetComponent(); if (reactor != null) { roundData.Reactors.Add(reactor); } } - pathFinder = new PathFinder(WayPoint.WayPointList, indoorsSteering: false); + pathFinder = new PathFinder(WayPoint.WayPointList, false); cachedDistances.Clear(); } @@ -192,7 +192,7 @@ namespace Barotrauma static CachedDistance CalculateNewCachedDistance(Character c) { - pathFinder ??= new PathFinder(WayPoint.WayPointList, indoorsSteering: false); + pathFinder ??= new PathFinder(WayPoint.WayPointList, false); var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(c.WorldPosition), ConvertUnits.ToSimUnits(Submarine.MainSub.WorldPosition)); if (path.Unreachable) { return null; } return new CachedDistance(c.WorldPosition, Submarine.MainSub.WorldPosition, path.TotalLength, Timing.TotalTime + Rand.Range(1.0f, 5.0f)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs index ac830c575..a2b4ba5cd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs @@ -1,21 +1,41 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Barotrauma.IO { static class Validation { - static readonly string[] unwritableDirs = new string[] { "Content", "Data/ContentPackages" }; + private static readonly string[] unwritableDirs = new string[] { "Content", "Data/ContentPackages" }; + private static readonly string[] unwritableExtensions = new string[] + { + ".pdb", ".com", ".scr", ".dylib", ".so", ".a", ".app", //executables and libraries (.exe and .dll handled separately in CanWrite) + ".bat", ".sh", //shell scripts + ".json" //deps.json + }; /// /// When set to true, the game is allowed to modify the vanilla content in debug builds. Has no effect in non-debug builds. /// public static bool SkipValidationInDebugBuilds; - public static bool CanWrite(string path) + public static bool CanWrite(string path, bool isDirectory) { path = System.IO.Path.GetFullPath(path).CleanUpPath(); + string extension = System.IO.Path.GetExtension(path).Replace(" ", ""); + if (unwritableExtensions.Any(e => e.Equals(extension, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + + if (!path.StartsWith(System.IO.Path.GetFullPath("Mods/").CleanUpPath(), StringComparison.OrdinalIgnoreCase) + && (extension.Equals(".dll", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".exe", StringComparison.OrdinalIgnoreCase))) + { + return false; + } + foreach (string unwritableDir in unwritableDirs) { string dir = System.IO.Path.GetFullPath(unwritableDir).CleanUpPath(); @@ -38,9 +58,9 @@ namespace Barotrauma.IO { public static void SaveSafe(this System.Xml.Linq.XDocument doc, string path) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot save XML document to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot save XML document to \"{path}\": modifying the files in this folder/with this extension is not allowed."); return; } doc.Save(path); @@ -48,9 +68,9 @@ namespace Barotrauma.IO public static void SaveSafe(this System.Xml.Linq.XElement element, string path) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot save XML element to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot save XML element to \"{path}\": modifying the files in this folder/with this extension is not allowed."); return; } element.Save(path); @@ -73,9 +93,9 @@ namespace Barotrauma.IO public XmlWriter(string path, System.Xml.XmlWriterSettings settings) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot write XML document to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot write XML document to \"{path}\": modifying the files in this folder/with this extension is not allowed."); Writer = null; return; } @@ -228,9 +248,9 @@ namespace Barotrauma.IO public static System.IO.DirectoryInfo CreateDirectory(string path) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, true)) { - DebugConsole.ThrowError($"Cannot create directory \"{path}\": modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot create directory \"{path}\": modifying the contents of this folder/using this extension is not allowed."); return null; } return System.IO.Directory.CreateDirectory(path); @@ -238,9 +258,9 @@ namespace Barotrauma.IO public static void Delete(string path, bool recursive=true) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, true)) { - DebugConsole.ThrowError($"Cannot delete directory \"{path}\": modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot delete directory \"{path}\": modifying the contents of this folder/using this extension is not allowed."); return; } //TODO: validate recursion? @@ -257,9 +277,9 @@ namespace Barotrauma.IO public static void Copy(string src, string dest, bool overwrite=false) { - if (!Validation.CanWrite(dest)) + if (!Validation.CanWrite(dest, false)) { - DebugConsole.ThrowError($"Cannot copy \"{src}\" to \"{dest}\": modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot copy \"{src}\" to \"{dest}\": modifying the contents of this folder/using this extension is not allowed."); return; } System.IO.File.Copy(src, dest, overwrite); @@ -267,12 +287,12 @@ namespace Barotrauma.IO public static void Move(string src, string dest) { - if (!Validation.CanWrite(src)) + if (!Validation.CanWrite(src, false)) { DebugConsole.ThrowError($"Cannot move \"{src}\" to \"{dest}\": modifying the contents of the source folder is not allowed."); return; } - if (!Validation.CanWrite(dest)) + if (!Validation.CanWrite(dest, false)) { DebugConsole.ThrowError($"Cannot move \"{src}\" to \"{dest}\": modifying the contents of the destination folder is not allowed"); return; @@ -282,9 +302,9 @@ namespace Barotrauma.IO public static void Delete(string path) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot delete file \"{path}\": modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot delete file \"{path}\": modifying the contents of this folder/using this extension is not allowed."); return; } System.IO.File.Delete(path); @@ -304,15 +324,15 @@ namespace Barotrauma.IO case System.IO.FileMode.OpenOrCreate: case System.IO.FileMode.Append: case System.IO.FileMode.Truncate: - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot open \"{path}\" in {mode} mode: modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot open \"{path}\" in {mode} mode: modifying the contents of this folder/using this extension is not allowed."); return null; } break; } return new FileStream(path, System.IO.File.Open(path, mode, - !Validation.CanWrite(path) ? + !Validation.CanWrite(path, false) ? System.IO.FileAccess.Read : access)); } @@ -334,9 +354,9 @@ namespace Barotrauma.IO public static void WriteAllBytes(string path, byte[] contents) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot write all bytes to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot write all bytes to \"{path}\": modifying the files in this folder/with this extension is not allowed."); return; } System.IO.File.WriteAllBytes(path, contents); @@ -344,9 +364,9 @@ namespace Barotrauma.IO public static void WriteAllText(string path, string contents, System.Text.Encoding? encoding = null) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot write all text to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot write all text to \"{path}\": modifying the files in this folder/with this extension is not allowed."); return; } System.IO.File.WriteAllText(path, contents, encoding ?? System.Text.Encoding.UTF8); @@ -354,9 +374,9 @@ namespace Barotrauma.IO public static void WriteAllLines(string path, IEnumerable contents, System.Text.Encoding? encoding = null) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot write all lines to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot write all lines to \"{path}\": modifying the files in this folder/with this extension is not allowed."); return; } System.IO.File.WriteAllLines(path, contents, encoding ?? System.Text.Encoding.UTF8); @@ -396,7 +416,7 @@ namespace Barotrauma.IO { get { - if (!Validation.CanWrite(fileName)) { return false; } + if (!Validation.CanWrite(fileName, false)) { return false; } return innerStream.CanWrite; } } @@ -422,13 +442,13 @@ namespace Barotrauma.IO public override void Write(byte[] buffer, int offset, int count) { - if (Validation.CanWrite(fileName)) + if (Validation.CanWrite(fileName, false)) { innerStream.Write(buffer, offset, count); } else { - DebugConsole.ThrowError($"Cannot write to file \"{fileName}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot write to file \"{fileName}\": modifying the files in this folder/with this extension is not allowed."); } } @@ -493,9 +513,9 @@ namespace Barotrauma.IO public void Delete() { - if (!Validation.CanWrite(innerInfo.FullName)) + if (!Validation.CanWrite(innerInfo.FullName, false)) { - DebugConsole.ThrowError($"Cannot delete directory \"{Name}\": modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot delete directory \"{Name}\": modifying the contents of this folder/using this extension is not allowed."); return; } innerInfo.Delete(); @@ -529,9 +549,9 @@ namespace Barotrauma.IO } set { - if (!Validation.CanWrite(innerInfo.FullName)) + if (!Validation.CanWrite(innerInfo.FullName, false)) { - DebugConsole.ThrowError($"Cannot set read-only to {value} for \"{Name}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot set read-only to {value} for \"{Name}\": modifying the files in this folder/with this extension is not allowed."); return; } innerInfo.IsReadOnly = value; @@ -540,7 +560,7 @@ namespace Barotrauma.IO public void CopyTo(string dest, bool overwriteExisting = false) { - if (!Validation.CanWrite(dest)) + if (!Validation.CanWrite(dest, false)) { DebugConsole.ThrowError($"Cannot copy \"{Name}\" to \"{dest}\": modifying the contents of the destination folder is not allowed."); return; @@ -550,9 +570,9 @@ namespace Barotrauma.IO public void Delete() { - if (!Validation.CanWrite(innerInfo.FullName)) + if (!Validation.CanWrite(innerInfo.FullName, false)) { - DebugConsole.ThrowError($"Cannot delete file \"{Name}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot delete file \"{Name}\": modifying the files in this folder/with this extension is not allowed."); return; } innerInfo.Delete(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs index 7f881cad9..a797bde55 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using Barotrauma.IO; using System.IO.Compression; @@ -352,8 +353,10 @@ namespace Barotrauma } } - public static bool DecompressFile(string sDir, GZipStream zipStream, ProgressDelegate progress) + private static bool DecompressFile(bool writeFile, string sDir, GZipStream zipStream, ProgressDelegate progress, out string fileName) { + fileName = null; + //Decompress file name byte[] bytes = new byte[sizeof(int)]; int Readed = zipStream.Read(bytes, 0, sizeof(int)); @@ -375,6 +378,8 @@ namespace Barotrauma sb.Append(c); } string sFileName = sb.ToString(); + + fileName = sFileName; progress?.Invoke(sFileName); //Decompress file content @@ -387,6 +392,17 @@ namespace Barotrauma string sFilePath = Path.Combine(sDir, sFileName); string sFinalDir = Path.GetDirectoryName(sFilePath); + + string sDirFull = (string.IsNullOrEmpty(sDir) ? Directory.GetCurrentDirectory() : Path.GetFullPath(sDir)).CleanUpPathCrossPlatform(correctFilenameCase: false); + string sFinalDirFull = (string.IsNullOrEmpty(sFinalDir) ? Directory.GetCurrentDirectory() : Path.GetFullPath(sFinalDir)).CleanUpPathCrossPlatform(correctFilenameCase: false); + + if (!sFinalDirFull.StartsWith(sDirFull, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException( + $"Error extracting \"{sFileName}\": cannot be extracted to parent directory"); + } + + if (!writeFile) { return true; } if (!Directory.Exists(sFinalDir)) Directory.CreateDirectory(sFinalDir); @@ -421,7 +437,7 @@ namespace Barotrauma { using (FileStream inFile = File.Open(sCompressedFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)) using (GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true)) - while (DecompressFile(sDir, zipStream, progress)) { }; + while (DecompressFile(true, sDir, zipStream, progress, out _)) { }; break; } @@ -434,6 +450,35 @@ namespace Barotrauma } } + public static IEnumerable EnumerateContainedFiles(string sCompressedFile) + { + int maxRetries = 4; + HashSet paths = new HashSet(); + for (int i = 0; i <= maxRetries; i++) + { + try + { + using FileStream inFile = File.Open(sCompressedFile, System.IO.FileMode.Open, System.IO.FileAccess.Read); + using GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true); + while (DecompressFile(false, "", zipStream, null, out string fileName)) + { + paths.Add(fileName); + } + } + catch (System.IO.IOException e) + { + if (i >= maxRetries || !File.Exists(sCompressedFile)) { throw; } + + DebugConsole.NewMessage( + $"Failed to decompress file \"{sCompressedFile}\" for enumeration {{{e.Message}}}, retrying in 250 ms...", + Color.Red); + Thread.Sleep(250); + } + } + + return paths; + } + public static void CopyFolder(string sourceDirName, string destDirName, bool copySubDirs, bool overwriteExisting = false) { // Get the subdirectories for the specified directory. diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 48c9afcb0..babb03713 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,50 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.2.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- A bunch more talents and talent-related items. +- Added a new "Return" order for ordering bots to return back to the main submarine. +- Bots can now use level waypoints to help them navigate around when they are outside the submarine. +- Characters can now only have a single "Movement" category order at a time. +- Added condition_out pin to various items. +- Adjusted the color of the status terminal to be more greener. +- Added door and hatch position indicators to status monitor. +- Made alerts and job icons in status monitor be consistent in size. +- Combined genetic materials show the descriptions of both of the materials in the tooltip. +- The talent menu is disabled when not controlling a human character or playing the campaign. +- Disabled toggling the sonar mode by pressing the Run key. +- Changed default creature attack key to F because R conflicts with the radio keybind. +- Adjustments to how far creatures can see and hear the submarine and it's devices from. Moving fast now makes more noise, moving slowly less, and the monsters can't see the sub from as far as before. Effectively it should now be more viable tactic to shut the engines down and keep silent. +- Reduce sonar ping's sound range from 10000 to 8000 to make it possible to spot (some) monsters before they target the sub. +- Made a couple of monsters unable to eat characters (hammerheads, terminal cells, leucocytes, molochs, spinelings and watchers). + +Fixes: +- Fixed crash when loading a container that has no containable restrictions and contains items (e.g. if you put items in a deconstructor and start a new round). +- Fixed bots not swapping oxygen tanks when they are outside and going to a target that is inside. +- Fixed issues with bot combat behavior when outside the submarine. +- Fixed ability to hold 2-handed items with one hand by trying insert them into an occupied slot in a container that can't hold the item. +- Fixed genetic materia's effects not always disappearing when unequipping the material (unstable only). +- Fixed light components staying powered indefinitely when in a container or inventory (didn't seem to be noticeable on any other vanilla items than sonar beacons, which stayed active indefinitely). +- Fixed some outpost events being possible to activate even if the target NPC is dead. +- Fixed ability to swap contained non-interactable items. +- Fixed inability to adjust max mission count in a dedicated server. +- Fixed ID overlaps when loading outpost modules that contain items which spawn with some contained item (e.g. alien battery cells or magazines). +- Fixed characters in the transition phase of a husk infection (i.e. after the stinger has appeared) getting stunned at the start of every round. +- Fixed cargo missions sometimes only rewarding the players for 1 crate even when transporting more. +- Fixed heavy scooter working even if the battery is not in the correct slot, added an icon to the battery slot (unstable only). +- Fixed the "use as treatment" tooltip showing up when trying to drop an item that can't be used as a treatment on the health interface. +- Fixed characters with spineling/raptor genes turning into husks when they die (unstable only). +- Fixed any amount of damage triggering mollusc gene's vigor buff (unstable only), making it easy to max the vigor with tools that do damage every frame (e.g. plasma cutter). +- Fixed genetic materials refining to 100% when combined with stabilozine (unstable only). +- Fixed gene splicer slot's tooltip staying visible when you close the health interface while your cursor is on the slot (unstable only). +- Fixed gene splicer slot sometimes being misaligned when opening the health interface for the 1st time (unstable only). +- Fixed concussion's description being used as its name (unstable only). +- Fixed ability to recursively stack bandoliers, toolbelts and heavy scooters. + +Modding: +- Option to make afflictions draw a full-screen overlay when active. + --------------------------------------------------------------------------------------------------------- v0.1500.1.0 --------------------------------------------------------------------------------------------------------- @@ -55,14 +102,13 @@ Additions and changes: - Added "high_pressure" output to water detector. - Water detectors round the water percentage output up, so any amount of water will be at least 1%. - Focus on the password field automatically in the server password prompt and allow submitting it with enter. -- Made pirates a little less accurate when they're operating turrets: they can no longer magically aim exactly at characters inside another sub. +- Made pirates a little less accurate when they're operating turrets: they can no longer magically aim exactly at characters inside another sub. - Biome noise loop volume is tied to sound volume instead of music volume. - Endworms no longer always bleed to death when their tail is cut. - Lever state is visualized on its sprite. - Enabled NVidia Optimus on Windows. - -Overhauled status monitor: +Overhauled status monitor: - Improved visuals. - Indicates the locations of the crew's ID cards. - Indicates the locations of alerts. @@ -88,6 +134,12 @@ Modding: - Option to create custom husk infections where player control carries over to the transformed creature. - Display a console warning when an item's deconstruct output defines an out condition and is also set to copy the condition of the deconstructed item. +--------------------------------------------------------------------------------------------------------- +v0.14.9.1 +--------------------------------------------------------------------------------------------------------- + +- Fixed an exploit that allowed modified servers to send malicious campaign save files to clients. + --------------------------------------------------------------------------------------------------------- v0.14.9.0 --------------------------------------------------------------------------------------------------------- @@ -159,7 +211,7 @@ Fixes: - 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 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. @@ -271,7 +323,7 @@ Changes: - Increased the stun of smg rounds from 0.125 to 0.15 to give it a bit more stopping power. - Lowered Husks' health regeneration and bleeding reduction. Crawler Husks now regenerate too. Lowered their health a bit to compensate it. - Hammerhead and Golden Hammerhead: Increased health, added some protection on claws and the tail end. Slightly increase the slow swimming speed and animations. The claws don't break anymore when shot with coilgun. -- Hammerheads and Husks don't avoid gun fire anymore. Pets now avoid gun fire. +- Hammerheads and Husks don't avoid gun fire anymore. Pets now avoid gun fire. - Removed Endworm's weak point in the mouth. - Most outpost events no longer trigger automatically, but require interacting with a specific item/character. - Taking items that contain stolen items counts as stealing, so you can't for example put a toolbox inside an outpost cabinet, load it full of items and then take it. @@ -439,7 +491,7 @@ Campaign changes: - Beacon stations are shown on the campaign map. - Visual changes to the map to make it a bit more intuitive. - When you enter a level with a beacon station, you always get an optional side objective to restore it even if you haven't selected a beacon mission. -- Added radiation on the campaign map: the intensity of the radiation around Jupiter is slowly increasing, which is forcing Europans to delve deeper under the ice. In practice, the radiation gradually destroys the outposts starting from the left side of the map, making it more dangerous and costly to stay in these areas. The intention behind this is to prevent players from farming resources indefinitely in the low-difficulty areas of the game before proceeding further. +- Added radiation on the campaign map: the intensity of the radiation around Jupiter is slowly increasing, which is forcing Europans to delve deeper under the ice. In practice, the radiation gradually destroys the outposts starting from the left side of the map, making it more dangerous and costly to stay in these areas. The intention behind this is to prevent players from farming resources indefinitely in the low-difficulty areas of the game before proceeding further. - Gating progress between biomes: you need a certain amount of money or reputation before you can enter the next biome. - Reworked exit points at uninhabited locations: there's a hole/tunnel above the start/end of the level the sub needs to enter to leave. @@ -824,7 +876,7 @@ Bots: - Bots should now properly respect ignored targets also when they are already targeting the item when you tell them to ignore it. - Fixed bots not equipping diving gear when the oxygen level is low and there is no leaks/water in the hull, causing them to suffocate. - Fixed bots "forgetting" autonomous operate (operate reactor or steer) orders if the objectives happen to fail. -- Fixed bots sometimes incorrectly abandoning a movement objective, when the path requires a diving gear. +- Fixed bots sometimes incorrectly abandoning a movement objective, when the path requires a diving gear. - Bots should now know how to get back inside through gaps in the hull. - Fixed NPCs not being able to repower the reactor if the player somehow manages to unpower it. - Bots now run when they are ordered to clean up things. @@ -957,7 +1009,7 @@ v0.11.0.10 - Fixed crashing when entering a new level in the campaign when an inactive pump has been infected with ballast flora. - Fixed ballast flora branches respawning instantly if they're destroyed while they're growing towards a target. - Fixed crashing when attempting to place components outside of the submarine in test mode. -- Fixed inability to rewire beacon stations when rewiring is disabled on the server. +- Fixed inability to rewire beacon stations when rewiring is disabled on the server. - Fixed repair tools that aren't held causing a crash upon use (only affects modded items). - Fixed raycast weapons (revolvers, shotguns, SMGs) sometimes not hitting monsters in specific areas outside the sub. - Fixed submarine's price field being difficult to edit in the sub editor due to the value getting clamped above the minimum price while typing in the box. @@ -1609,7 +1661,7 @@ v0.10.3.0 --------------------------------------------------------------------------------------------------------- - Fixed SalvageMission not spawning the item if the mission has been attempted previously, causing an "attempted to pick up a removed item" error when trying to pick up the artifact/logbook. -- Fixed bots going to operate the reactor/navterminal in the main sub when they are inside the outpost. Now they should only be allowed to do this when ordered to. +- Fixed bots going to operate the reactor/navterminal in the main sub when they are inside the outpost. Now they should only be allowed to do this when ordered to. - Fixed mechanic tutorial getting softlocked if the oxygen tanks are put in the deconstructor without putting them in the player inventory first (e.g. by putting them inside a diving mask and moving them from there to the deconstructor). - Fixed "failed to spawn item, component index out of range" error when an item that originally spawned in a container has been moved inside another container whose ItemContainer component doesn't have the same index as the previous one (e.g. when moving items from cabinets in a wreck into a toolbox). - Fixed dialog prompts in the "clownrelations1" and "engineers_are_special" events being displayed to all players in the server. @@ -1762,7 +1814,7 @@ Additions and changes: - Added 2 new moloch variants: Black Moloch and Moloch Pupa. - Reworked Moloch. - Overhauled level layouts and events (longer and more difficult levels). -- Added two new afflictions: medical items and poisons cause organ damage instead of internal damage and explosions cause deep tissue injuries. Both are functionally identical to internal damage, and treated with the same items. +- Added two new afflictions: medical items and poisons cause organ damage instead of internal damage and explosions cause deep tissue injuries. Both are functionally identical to internal damage, and treated with the same items. - Added DXT5 texture compression to reduce memory consumption. Slightly increases loading times; if you're not short on memory, you may want to disable the compression from the game settings. - Added partial dismemberment for live creatures. Currently enabled only for non-humanoids. (Dismembering dead bodies was already in the game). - Destructible shells/armor -> Moloch's shell can now be destroyed. @@ -1797,7 +1849,7 @@ Additions and changes: - Hulls can be multiedited in the sub editor. - Placed down wires can now be re-equipped in the sub editor by double clicking a loose end. - Added charging docks to Remora. -- Adjusted how pixel sizes are converted to meters (which are used to display the submarine's dimensions and distances on the navigation terminal). Previously 100 pixels corresponded to 1 meter, now it's 80px -> 1m, making the human characters about 1.75m tall. +- Adjusted how pixel sizes are converted to meters (which are used to display the submarine's dimensions and distances on the navigation terminal). Previously 100 pixels corresponded to 1 meter, now it's 80px -> 1m, making the human characters about 1.75m tall. - Distance calculations on the navigation terminal take the shape of the path into account instead of just using the direct distance to the target. - Made improvements to the manual order assignment by adding always visible name labels, displaying indicators for characters' current orders, and repositioning the nodes. - Reduced the damage range of fires, characters don't take damage from fires if there's a closed door or a wall in between. @@ -2013,7 +2065,7 @@ Bugfixes: - Fixed enablecheats command not being relayed to server. - Fixed light component and alarm siren/buzzer states occasionally getting desynced. - Fixed inability to enter the sub through very small hulls. -- Fixed antibiotics not giving husk infection resistance when shot from a syringe gun. +- Fixed antibiotics not giving husk infection resistance when shot from a syringe gun. - Fixed text overflows in the player management panel in the server lobby in languages other than English. - Fixed searchlight toggle doing nothing. - Fixed hulls that have minuscule amounts of water in them (too small to be even rendered) being able to trigger InWater effects and water footstep sounds. @@ -2173,7 +2225,7 @@ UI/UX improvements: - Periscopes can be deselected by pressing esc. - Fabricators can pull ingredients directly from the user's inventory without having to place them in the fabricator's input slots. - Lock the on/off switch in the pump interface when the state is controlled by signals, same with the engine slider. -- 1 second cooldown before doors can be opened/closed after someone else has opened/closed them. Makes it less likely for doors to be opened/closed accidentally when multiple people are trying to use them at the same time. +- 1 second cooldown before doors can be opened/closed after someone else has opened/closed them. Makes it less likely for doors to be opened/closed accidentally when multiple people are trying to use them at the same time. - Show a warning if trying to start a campaign for the first time without playing the tutorials. - Diving suits and fire extinguishers are not automatically picked up from the lockers/brackets when clicking on them to make it less likely to accidentally pick them up. Instead, clicking on them opens the inventory of the container, the same way when interacting with e.g. a steel cabinet. - Subinventories (= inventories inside items, toolboxes for example) open/close faster and cannot be interacted with until fully open. @@ -2279,7 +2331,7 @@ AI: - Replaced the generic "cannot reach target" messages with context-specific and more descriptive messages. - Bots now take the other bots into account when they evaluate the importance of the tasks. Fixes multiple bots going to fix the same leaks or repair the same items. - Bots should now abandon the combat objective only when not fleeing from an enemy. If they fail to flee from an enemy, they will fight (or avoid) instead. -- Fixed bots loading the turrets only with the default ammunition. +- Fixed bots loading the turrets only with the default ammunition. - Fixed multiple bots trying to navigate the submarine at the same time. - Fixed pathfinding applying 10x more penalty on vertical distance when the host is outside (should only apply inside). - Fixed bots starting the path from obstructed waypoints or waypoints that are inside when they are outside or vice versa. @@ -2319,7 +2371,7 @@ Monsters: - Fixed Mudraptors sometimes squeezing themselves towards doors without being able to attack them. - Fixed monsters not reacting to being fired with turrets unless they can target the attacker. - Fixed minor slipping in Mudraptor's walking animation. -- Weapons and tools now have ai targets that are only activated when the items are used -> shooting monsters should make you much more attractive target than just swimming peacefully around. +- Weapons and tools now have ai targets that are only activated when the items are used -> shooting monsters should make you much more attractive target than just swimming peacefully around. Multiplayer: - Fixed a bunch of bugs that caused "missing entity" errors. However, there are many different reasons the error can occur, so even though we have not run into the issue anymore during out testing rounds, there is still a chance it may occur in some situations. @@ -2435,7 +2487,7 @@ Electricity fixes: - Fixed LightComponents being toggled twice when they receive a signal to the toggle connection. Misc fixes: -- Fixed "last used" listbox overlapping with the entity visibility tickboxes in the submarine editor on low resolutions. +- Fixed "last used" listbox overlapping with the entity visibility tickboxes in the submarine editor on low resolutions. - Fixed misaligned colliders on the "Shell A Cap 0 deg A/B" wall pieces. - Fixed links disappearing between linked subs and docking ports when loading in the sub editor. - Fixed loading freezing for up to 10 seconds if the game cannot fetch the remote content for the main menu (update notifications, changelogs, etc). @@ -2487,9 +2539,9 @@ Misc additions and changes: - A new submarine, Kastrull. - 6 new traitor missions with much more varied objectives. - More variants of all job outfits. -- 30 new character face sprites. +- 30 new character face sprites. - Overhauled job assignment logic to make the job distribution a little more balanced. Now each client gets assigned one of the spawnpoints (and its associated job) according to their job preference, which means that if the sub for example has 2x as many engineer spawnpoints than medic spawnpoints, there tend to be 2x more engineers. When playing with a mod that adds new jobs to the game, spawnpoints that have no job associated with them are considered spawnpoints for the non-vanilla jobs. - - We would like to get feedback from players about this: does the job assignment seem fair, are people generally getting the jobs they want? + - We would like to get feedback from players about this: does the job assignment seem fair, are people generally getting the jobs they want? - New logic components: sin, cos, tan, asin, acos, atan, modulo, round, ceil, floor and factorial. - Improved autopilot: much faster and less likely to get stuck. - We would like to get feedback from players about this: Is there still incentive to steer manually? @@ -2701,8 +2753,8 @@ tags, it will replace the vanilla wrench. - The husk affliction can now be modified and applied on any character. - Support for multiple simultaneous husk afflictions based on the same system. - Support for multiple husk appendages. Made the attachment limb configurable in the affliction definition. -- Additional content package validity checks during startup: make sure all XML files in the package can -be loaded, and disable the package if they can't. Fixes tons of console errors on startup when a mod with +- Additional content package validity checks during startup: make sure all XML files in the package can +be loaded, and disable the package if they can't. Fixes tons of console errors on startup when a mod with invalid XML files is enabled. - Don't allow publishing workshop items that contain invalid XML files. - Don't allow selecting invalid content packages in the settings menu. @@ -2727,7 +2779,7 @@ drawn on the character. - Exposed structure sound types. - Exposed the mouth position. - Exposed the angular damping and density for limbs (removed mass, which was not used). -- Added a default texture path for ragdolls so that the texture can only be defined once. Limb specific +- Added a default texture path for ragdolls so that the texture can only be defined once. Limb specific texture definitions override this. - Allow to define a group for different species. The characters in the same group are friendly to each other. - DecorativeSprites can now be used on character limbs (like on items). @@ -2749,7 +2801,7 @@ by holding shift. - Added "pause" console command (only usable in single player). Bugfixes: -- Fixed crashing when the recharge speed of a PowerContainer with no interface (e.g. Alien Generator) is +- Fixed crashing when the recharge speed of a PowerContainer with no interface (e.g. Alien Generator) is adjusted by a signal or a bot. - Fixed docking interface becoming active on navigation terminals when any of the submarine's docking ports are close to another docking port, even if the terminal in question is not wired to that port. @@ -2757,18 +2809,18 @@ ports are close to another docking port, even if the terminal in question is not - Fixed clients not creating a download prompt when a sub they don't have is selected by vote. - Fixed traitor missions almost always placing the mission-related items inside the same containers. - Fixed traitor goal durations being displayed as "duration(xx) seconds" instead of "xx seconds". -- Fixed Traitor's "find an item" objectives not being considered complete if the target item is inside +- Fixed Traitor's "find an item" objectives not being considered complete if the target item is inside another item within the traitor's inventory. -- Fixed server using the provided campaign savefile name as-is (without the required .save file extension) -when starting a new campaign through the console. Caused clients to throw "File transfer failed (wrong +- Fixed server using the provided campaign savefile name as-is (without the required .save file extension) +when starting a new campaign through the console. Caused clients to throw "File transfer failed (wrong file extension """!)" errors and prevented them from receiving the save files. -- Fixed servers being able to start the round multiple times by spamming the "start" console command +- Fixed servers being able to start the round multiple times by spamming the "start" console command before loading the round finishes. - Fixed rewiring sound playing whenever a remote player is using a rewireable device. - Fixed subinventories not opening when grabbing another character with no items in the corresponding slot. -- Fixed draggable inventories getting stuck to a half-open state if the item is equipped when +- Fixed draggable inventories getting stuck to a half-open state if the item is equipped when the inventory is opening/closing. -- Fixed light sprites being mirrored when the item is mirrored, even if the mirroring the item's sprite +- Fixed light sprites being mirrored when the item is mirrored, even if the mirroring the item's sprite had been disabled (e.g. junction boxes). - Fixed motion sensor detection area not being flipped when the item is mirrored. - Fixed status monitor not mirroring rooms on the display in mirrored subs. @@ -2779,12 +2831,12 @@ had been disabled (e.g. junction boxes). - Fixed damaged item sprites that are set to fade in according to the damage always being drawn at full opacity. - Fixed spectators being distributed into teams in combat missions, potentially leading to imbalanced crew sizes. - Notify the client using the "togglekarmatestmode" command about the test mode being enabled/disabled. -- Send karma change notifications when karma has changed by 1 unit or more when test mode is enabled, not +- Send karma change notifications when karma has changed by 1 unit or more when test mode is enabled, not just when an action causes an immediate change of 1 unit or more. - Fixed adjacent sprites bleeding into the platform and topwindow sprites. - Fixed autocompleting submarine/shuttle names when using the submarine/shuttle console commands. - Fixed some items (like sonar beacon) attracting monsters even when they're powered off. -- Fixed inability to open the pause menu if an inventory slot had been highlighted when exiting +- Fixed inability to open the pause menu if an inventory slot had been highlighted when exiting the game screen. - Fixed welded door sprites "twitching" when the submarine moves. - Fixed crashing when a character with no hands or arms drops a holdable item. @@ -2792,12 +2844,12 @@ the game screen. - Traitor missions are considered unsuccessful if the objectives cannot be completed (for example if the submarine doesn't have suitable containers to place the traitor items inside). - Fixed the "Barotrauma" title text staying invisible in the main menu when coming back from the credits. -- Fixed clients not refreshing an item's editing hud when a remote player adjusts the values (i.e. if two -players had selected the same lamp and one changed the color value, the other client wouldn't see +- Fixed clients not refreshing an item's editing hud when a remote player adjusts the values (i.e. if two +players had selected the same lamp and one changed the color value, the other client wouldn't see the value change in the editing hud). - Fixed a couple of the additive light sprites being slightly offset from the lamp. -- Clients don't display client-side vitality changes in healthbars until the actual vitality is received -from the server. Fixes health occasionally dropping and then jumping back up if the client predicts damage +- Clients don't display client-side vitality changes in healthbars until the actual vitality is received +from the server. Fixes health occasionally dropping and then jumping back up if the client predicts damage incorrectly (e.g. if a melee attack hits client-side but doesn't server-side). - Fixed character syncing being very inaccurate when switching to freecam and spectating a character far away from other players. @@ -2851,10 +2903,10 @@ Caused potassium and magnesium to explode continuously client-side when immersed - Fixed occasional "error while reading a message from the server" console errors when joining a server. - Fixed clients occasionally timing out and disconnecting during the loading screens. - Fixed character name box resetting in the server lobby when another client joins or disconnects. -- Fixed "collection was modified" error when a client who's been given control of a bot tries to use +- Fixed "collection was modified" error when a client who's been given control of a bot tries to use the report buttons. - Fixed server hanging when a client joins if the server has a very large number of submarines installed. -- Fixed a bunch of client-side console commands crashing the game if the client disconnects while a question +- Fixed a bunch of client-side console commands crashing the game if the client disconnects while a question prompt is active and then enters something in the console. - Fixed full SteamP2P servers not showing up on the server list regardless of the filters. - Fixed "remove ban" and "range ban" buttons in the server settings menu doing nothing. @@ -2866,17 +2918,17 @@ prompt is active and then enters something in the console. - Fixed scroll values resetting in the multiplayer campaign store menu when purchasing/selling items. Miscellaneous improvements: -- Attempting to drop an item to an empty inventory slot that's not of the right type (e.g. trying to put +- Attempting to drop an item to an empty inventory slot that's not of the right type (e.g. trying to put an extinguisher in the normal inventory slots), automatically moves the item to the correct slot if it's free. - Made irrelevant items (light components, lamps, small automated pumps, etc) non-interactable in the tutorials. -- The first patient cannot be healed in the doctor tutorial until the "take the patient to the medbay" +- The first patient cannot be healed in the doctor tutorial until the "take the patient to the medbay" objective has been completed. - The docking interface does not become active in the captain tutorial when leaving the first outpost. - Added sounds for rewiring and repairing. Miscellaneous fixes: - Fixed skills not having any effect on repair durations. -- Fixed inability to enable content packages created for a version prior to 0.9.2.0 (ones that have the +- Fixed inability to enable content packages created for a version prior to 0.9.2.0 (ones that have the content package in the Data/ContentPackages folder instead of in the mod folder). - Use a welding tool icon to indicate damaged walls in the mechanic tutorial, because the generic repair icon caused some players to think the wall needs to be repaired with a wrench. @@ -2884,13 +2936,13 @@ icon caused some players to think the wall needs to be repaired with a wrench. - Reduced repair durations in the engineer/mechanic tutorials. - Fixed draggable subinventories staying visible when stunned. - Fixed light sprites not being scaled in the sub editor when resizing lamps. -- Fixed contained items not changing their position when flipping the container (e.g. oxygen generator +- Fixed contained items not changing their position when flipping the container (e.g. oxygen generator with some tanks inside). - Fixed projectiles emitting sparks when they hit a character (not just when they hit a structure). - Fixed sonar's zoom slider not moving when autodocking zooms in. - Fixed small creatures being unable to reach waypoints. - Fixed bots getting stuck while trying to repair items (in practice hatches) while holding ladders. -- Hid SMG rounds and coilgun bolts from the sub editor because they're not usable by themselves, but are +- Hid SMG rounds and coilgun bolts from the sub editor because they're not usable by themselves, but are spawned automatically when the SMG/coilgun is fired. - Hid the junction box tutorial variant from the sub editor. - Stun guns and darts can be fabricated and purchased. @@ -2900,21 +2952,21 @@ difficult to hit the crawler after it falls to the ground). - Added confirmation popups when exiting the sub editor or character editor to prevent losing unsaved work. - Fixed reactor warning lights being clickable and becoming invisible when pressed. - Fixed CPR button becoming invisible when pressed. -- Fixed water occasionally flowing only through one gap in a hull, for example water only draining -through a hole at a one side of a room even if there is another hole at the other side of the room. +- Fixed water occasionally flowing only through one gap in a hull, for example water only draining +through a hole at a one side of a room even if there is another hole at the other side of the room. - Fixed scrollbars not resizing when filtering lists (e.g. the submarine list in the "new game" menu). - Fixed being able to fire flamers (and other repair tools) through walls and doors. - Fixed flamers working underwater when the water is shallow enough for the character to stand. -- Fixed battery recharge rate slider not moving when the recharge rate is set by an incoming signal +- Fixed battery recharge rate slider not moving when the recharge rate is set by an incoming signal or a remote player. - Fixed some connection panels being rewireable in vanilla subs when a wire is equipped. - Fixed some pre-placed body armors and grenades in vanilla subs having an incorrect scale. -- Fixed crashing when a character gains a skill level on a skill that's not initially configured +- Fixed crashing when a character gains a skill level on a skill that's not initially configured for the character class. -- Fixed "Add File" dialog of the Workshop publish screen always using the mod's root folder as the file +- Fixed "Add File" dialog of the Workshop publish screen always using the mod's root folder as the file path even if the file is in a subfolder. - Fixed contentpackage version number not being updated when updating an older workshop item. -- Fixed subinventories closing when items are dropped into or removed from them, which made reloading +- Fixed subinventories closing when items are dropped into or removed from them, which made reloading weapons or putting several items into a container cumbersome. --------------------------------------------------------------------------------------------------------- @@ -2923,7 +2975,7 @@ v0.9.2.2 Karma improvements: - Attacking someone who has just recently attacked you doesn't reduce karma. -- The karma penalty from attacking someone scales according to their karma (i.e. smaller penalty for +- The karma penalty from attacking someone scales according to their karma (i.e. smaller penalty for attacking a griefer whose karma is low). - Damaging characters by performing CPR doesn't decrease karma. - Fixed karma decreasing when moving a wire from a connection to another. @@ -2935,9 +2987,9 @@ value of 50. Bugfixes: - Fixed mechanic tutorial crashing after the welding task when playing in Russian. -- Less restrictive client name symbol constraints. Fixes clients failing to join servers if their name +- Less restrictive client name symbol constraints. Fixes clients failing to join servers if their name contains Chinese symbols for example. -- Fixed inability to enable/update content packages created for a version prior to 0.9.2.0 (ones that have +- Fixed inability to enable/update content packages created for a version prior to 0.9.2.0 (ones that have the content package in the Data/ContentPackages folder instead of in the mod folder). --------------------------------------------------------------------------------------------------------- @@ -2953,20 +3005,20 @@ v0.9.2.1 - Fixed server crashing when attempting to greet a traitor with no objective. - Fixed dead characters being occasionally selected as traitors. - Fixed info menu still displaying the old traitor objective after a traitor dies and respawns. -- Allow traitors to re-use the sabotage button when someone starts to repair the device without having +- Allow traitors to re-use the sabotage button when someone starts to repair the device without having to re-open the interface. - Fixed grenades exploding multiple times when triggered using a detonator. - Scaled down alien materials (oxygenite, sulphurite, fulgurium, etc) and fixed their collider sizes. - Karma doesn't spontaneously decay/increase when dead. - Increased karma penalties for poisoning someone. - Option to kick clients with low karma from the server and ban them if they get kicked more than x times. -- Space herpes randomly toggles inverted controls on/off to make it more difficult to bypass the effect +- Space herpes randomly toggles inverted controls on/off to make it more difficult to bypass the effect by modifying keybinds. -- Fixed workshop item preview image not being refreshed in the publish tab when adding a preview image +- Fixed workshop item preview image not being refreshed in the publish tab when adding a preview image to an item that doesn't have one. -- Fixed "error loading submarine" console error when creating a copy of a submarine, deleting the original +- Fixed "error loading submarine" console error when creating a copy of a submarine, deleting the original sub and then saving the copy. -- Fixed server logging an "attempted to send a chat message to a null client" error when a client tries +- Fixed server logging an "attempted to send a chat message to a null client" error when a client tries to send a private message to a non-existent client. - Enable dynamic range compression and VOIP attenuation by default. - Fixed inability to extinguish fires through holes in walls. @@ -2986,15 +3038,15 @@ Easier server hosting: host to forward their ports. Expanded traitor feature: -- Multi-step traitor missions that not only make things more interesting to the traitors themselves but +- Multi-step traitor missions that not only make things more interesting to the traitors themselves but also to give the rest of the crew a fighting chance of detecting the traitor. Anti-griefing: -- Karma: a system that detects malicious actions and automatically creates a more challenging experience -for griefers, or potentially triggers a kickban. The feature is completely optional and the server hosts -can decide how aggressively it will react to malicious actions. In a nutshell, you can lose karma for doing -dumb things, karma is regained gradually over time and may be increased more rapidly by doing good things, -thus negating false positives and also giving you the chance to redeem yourself. Lose too much karma and +- Karma: a system that detects malicious actions and automatically creates a more challenging experience +for griefers, or potentially triggers a kickban. The feature is completely optional and the server hosts +can decide how aggressively it will react to malicious actions. In a nutshell, you can lose karma for doing +dumb things, karma is regained gradually over time and may be increased more rapidly by doing good things, +thus negating false positives and also giving you the chance to redeem yourself. Lose too much karma and you'll start to experience increasing levels of inconvenience. - Wire griefing is more difficult: wires have to be disconnected from the connection panels at both ends before they can be removed. Disconnected wires can be seen visibly hanging from the device, with noticeable @@ -3015,10 +3067,10 @@ New submarine: Bugfixes: - Fixed crashing when selecting doors or gaps in the sub editor. - Fixed crashing when combining items inside an ItemContainer (e.g. cabinet, deconstructor). -- Fixed shuttles not being able to redock into some submarines with unconventionally positioned docking +- Fixed shuttles not being able to redock into some submarines with unconventionally positioned docking ports. Specifically, if a port needed to be docked to from below but was positioned above the center of the submarine (or vice versa), the docking interface would not activate on the navigation terminal. -- Fixed docking port using the wrong submarine's position for joint adjustment, causing errors when +- Fixed docking port using the wrong submarine's position for joint adjustment, causing errors when docking a submarine with greater mass to one with a smaller mass. - Fixed an issue that caused occasional ID mismatch errors if a client died during a multiplayer campaign round and disconnected before the round ended. @@ -3030,49 +3082,49 @@ campaign round and disconnected before the round ended. - Fixed inability to combine items in multiplayer. - Fixed occasional "index out of range" errors when loading walls that have been set to a non-default scale in the submarine editor. -- Fixed inability to scroll the crew list with the mouse wheel when the cursor is over certain parts +- Fixed inability to scroll the crew list with the mouse wheel when the cursor is over certain parts of the list. - Fixed sonar pings stopping mid-way when active sonar is turned off, which could be exploited to stop the pings before they reach a monster further away from the sub. - Fixed clients not seeing other characters when entering spectator mode after their character has been eaten by a creature. -- Fixed clients not seeing other characters in spectator mode after the distance between the submarine +- Fixed clients not seeing other characters in spectator mode after the distance between the submarine and the client's corpse gets great enough. -- Fixed clients getting stuck to a non-functional game screen if they start a new round before the ending +- Fixed clients getting stuck to a non-functional game screen if they start a new round before the ending cinematic has finished server-side. - Fixed projectiles hitting a character you're standing next to when firing. - Fixed characters automatically equipping handcuffs (i.e. handcuffing themselves) if they were picked up when the only free inventory space was the hand slots. -- Fixed a rare race condition that occasionally caused the game to crash during the loading screen with +- Fixed a rare race condition that occasionally caused the game to crash during the loading screen with a "no text packs available in English!" error message. -- Fixed husk infection being healable with broad-spectrum antibiotics even when the infection has reached +- Fixed husk infection being healable with broad-spectrum antibiotics even when the infection has reached the final stage. -- Fixed attachable items (detonators, electrical components) becoming deattachable without any tools +- Fixed attachable items (detonators, electrical components) becoming deattachable without any tools if the sub is saved after deattaching them. -- Fixed projectile raycasts not taking wall rotation into account, causing the projectiles to hit sloped +- Fixed projectile raycasts not taking wall rotation into account, causing the projectiles to hit sloped walls when standing next to them. -- Fixed PowerTransfer components checking overloads based on the item the recursive power check starts from, +- Fixed PowerTransfer components checking overloads based on the item the recursive power check starts from, not the item that's currently being checked. Caused junction boxes to never break or catch fire in some subs - Fixed crashing in the character editor when no textures were found for the selected character. - Fixed crashing if a humanoid character has no knees or ankles. - Fixed EventManager calculating flooding amount incorrectly, causing floods to only have a very small effect on the intensity value. -- Fixed batteries and supercapacitors being able to provide power through their signal connections +- Fixed batteries and supercapacitors being able to provide power through their signal connections (e.g. "set_charge_rate", "charge_rate_out"). -- Lighting fix: obstruct background lights behind hulls. Previously the background lights would only get -obstructed by background structures, causing light to "bleed through" parts with no structures (e.g. +- Lighting fix: obstruct background lights behind hulls. Previously the background lights would only get +obstructed by background structures, causing light to "bleed through" parts with no structures (e.g. humpback's docking port) and windows in the background walls. - Fixed item loading being interrupted if any item XML cannot be loaded, causing some items not to be loaded if any of the selected content packages are missing files or contain corrupted item XMLs. - Fixed fire sounds not playing when standing inside a fire source. -- Fixed client names being converted to lower case when comparing them to the name written by the sender, +- Fixed client names being converted to lower case when comparing them to the name written by the sender, preventing the message from being sent if the name wasn't written in lower case. - Pass private messages to the client hosting the server when the message is targeted to the server. - Private messages appear in the server log. - Fixed plasma cutter and welding tool hit particles being offset from the flame. -- Fixed steel bar and titanium-aluminium alloy deconstructing to 100% condition items even when degraded +- Fixed steel bar and titanium-aluminium alloy deconstructing to 100% condition items even when degraded (which could be used as an exploit to get infinite coilgun rounds). -- Fixed holdable items staying in the characters hand(s) when swapping them from a hand slot to another +- Fixed holdable items staying in the characters hand(s) when swapping them from a hand slot to another limb slot (for example, when moving a flashlight from a hand slot to the head slot). - Fixed text wrapping not working properly with words that are longer than a single line. - Fixed deconstructor losing some of the material from deconstructed items if the output container couldn't hold all of the new items. @@ -3080,7 +3132,7 @@ limb slot (for example, when moving a flashlight from a hand slot to the head sl - Fixed a crash when retrieving lobby information with an uninitialized IP. - Fixed max player count in server list & duplicate lobby entries. - Fixed arrow keys not changing caret position when typing. -- Fixed doors looking repaired as soon as the condition goes above 0%, even though the collisions aren't +- Fixed doors looking repaired as soon as the condition goes above 0%, even though the collisions aren't re-enabled until the door gets above 50%. - Fixed water not leaking through broken doors. - Fixed inability to throw items through platforms. @@ -3095,26 +3147,26 @@ from "Content/Characters". Fixes mods not being able to load animation/ragdoll f are not defined explicitly in the character configuration file. - Improved performance by deleting dead monsters that are far away from the sub and by disabling physics on dead bodies when they stay still long enough. -- Added option to use conditionals to activate/deactivate ItemComponents. You can take a look at the +- Added option to use conditionals to activate/deactivate ItemComponents. You can take a look at the reactor and junction box config files to see how it's used. - Added some indicator lights to junction boxes and reactors. - Stun batons don't affect large enemies (molochs, hammerheads, endworms, etc). - In wiring mode, items are selected by pressing E instead of clicking. Selecting items with the left mouse button made it very difficult to manipulate the wires because it was easy to accidentally select -some device instead of a wire node. +some device instead of a wire node. - Crate/toolbox inventories stay open and can be moved across the screen when the item is equipped. - Added limits to submarine name and description length. - Delete incomplete file downloads when disconnecting from a server while a file transfer is active. Prevents console errors about corrupted submarine/save files caused by the partially downloaded files. - Increased Humpback's battery capacity. - Added widgets for manipulating coilgun/railgun rotation limits in the sub editor. -- Added a command for setting the value of a property on all selected entities in the sub editor +- Added a command for setting the value of a property on all selected entities in the sub editor (for example, "setentityproperties scale 2" would set the scale of all selected items/structures to 2). -- Show ballast tanks and airlocks in a different color on the status monitor to make it easier to +- Show ballast tanks and airlocks in a different color on the status monitor to make it easier to distinguish which rooms are actually flooding and which are supposed to have water in them. - Option to define multiple inventory variants for a character (e.g. to add variation to monster loot). - Start playing the main menu music during the loading screen. -- More reliable human walk sounds (played at specific points of the walk cycle instead of relying on +- More reliable human walk sounds (played at specific points of the walk cycle instead of relying on impacts between the feet and the floor). - Option to show a 16x16 grid and snap the cursor to it in the sprite editor. - Reactor can be controlled with movement keys. @@ -3134,7 +3186,7 @@ Additions and changes: - Toolboxes can be fabricated and decontructed. - Nerfed nitroglycerin's structure damage and increased the impact tolerance from 4 to 6. - Made alien pistols a little less underwhelming (sounds, recoil, particle effects). -- Deconstructors drop all items that are inside the deconstructed item. Previously they would just get destroyed, +- Deconstructors drop all items that are inside the deconstructed item. Previously they would just get destroyed, making it a potential exploit to destroy items that should not be possible to deconstruct. - Modified welding fuel tank and oxygen tank sprites a bit to differentiate them from each other a bit more. - Modified plasma cutter and welding tool sprites a bit to differentiate them from each other a bit more. @@ -3142,7 +3194,7 @@ making it a potential exploit to destroy items that should not be possible to de - Server list can be sorted according to ping, name, compatibility or any of the other property in the list. - Docking ports of the enemy submarine are not shown on the sonar during combat missions. - Recharge headset batteries between rounds in single player. -- Disable status monitor displays when out of power. +- Disable status monitor displays when out of power. - Reduce server CPU usage. - Update ladder and gap references if a waypoint is moved around in the editor. - More descriptive error messages when publishing a Workshop item fails. @@ -3162,23 +3214,23 @@ making it a potential exploit to destroy items that should not be possible to de changed by editing a parameter called "allowrewiring" in serversettings.xml. Misc bugfixes: -- Fixed the position of an outpost's docking port not being taken into account when determining how to -place it in the level. Caused large submarines sometimes to collide with walls when docked to outposts +- Fixed the position of an outpost's docking port not being taken into account when determining how to +place it in the level. Caused large submarines sometimes to collide with walls when docked to outposts where the port is offset from the center. - Fixed corrupted sub files causing crashes. -- Fixed item editing HUD not appearing on any other item after one item has been selected in-game +- Fixed item editing HUD not appearing on any other item after one item has been selected in-game (e.g. editing the channel on a wifi component prevented editing other items). -- Fixed ladders and other resizeable items reverting to their original size if they're resized and +- Fixed ladders and other resizeable items reverting to their original size if they're resized and the sub is saved and reloaded. - Fixed inability to resize gaps in the sub editor after they've been placed. -- Reactors take other power sources into account when calculating how much power they need to generate. +- Reactors take other power sources into account when calculating how much power they need to generate. Fixes overloads on Humpback when turning on the backup batteries and operating the reactor normally. - Fixed watchmen not retaliating when a character does very small amounts of damage to them. - Fixed bots being able to take handcuffs off from themselves. - Fixed waypoint and spawnpoints being selectable in the sub editor even if they're hidden. - Fixed deconstructing coilgun ammo boxes giving out more materials than the materials required to fabricate them. - Scrollbars can only be dragged if the mouse button is pressed down while the cursor is on the scrollbar, -not by holding the button and moving it on the scrollbar. Fixes accidentally switching from slider +not by holding the button and moving it on the scrollbar. Fixes accidentally switching from slider to another (often happens in the reactor interface). - Fixed crashing when setting limb or joint scale to 0 using console commands. - Fixed dropdowns in the Workshop item publish menu being draw behind the buttons below them. @@ -3186,7 +3238,7 @@ to another (often happens in the reactor interface). - Ignore keyboard inputs (delete, arrow keys, copy/paste) in the sub editor when a textbox is selected. Prevents accidentally deleting items/structures when attempting to delete text from a textbox. - Toolboxes can't be put inside other toolboxes or in the doctor's clothes. -- Fixed items bought from the store during a singleplayer campaign session being deducted from the credits +- Fixed items bought from the store during a singleplayer campaign session being deducted from the credits when save & quitting from the map. - Flares stop emitting particles when inside an inventory. - Fixed oxygen not flowing through horizontal gaps between subs (e.g. between Remora and the drone). @@ -3197,19 +3249,19 @@ being split into separate buttons when playing the game in Chinese. - Fixed "Text Self_CauseOfDeathDescription.Unknown not found" console error if a character gets killed by being inside a respawn shuttle when it despawns. - Fixed sonar not scaling properly when resolution is changed mid-round. -- Fixed fabricators & deconstructors displaying the "insufficient power" warning when power is low +- Fixed fabricators & deconstructors displaying the "insufficient power" warning when power is low (but still high enough for the devices to run). Networking fixes: -- Fixed another ID mismatch problem that occasionally caused clients to get kicked out in multiplayer, +- Fixed another ID mismatch problem that occasionally caused clients to get kicked out in multiplayer, usually with an error message warning about a missing item. - Fixed players always getting range banned when banned by a client. -- Fixed turrets not being aimed correctly in multiplayer if they're in another sub (e.g. the coilgun in +- Fixed turrets not being aimed correctly in multiplayer if they're in another sub (e.g. the coilgun in Remora's drone). - More reliable fabricator and deconstructor syncing. Should fix items disappearing when multiple players attempt to use the device at the same time and strange timing inconsistencies when deconstructing multiple items in succession. -- Fixed all servers showing "unknown" as the game mode in the server list. +- Fixed all servers showing "unknown" as the game mode in the server list. - Fixed server not allowing forward slashes in host messages. - Fixed repair interface sometimes getting stuck close to 100% in multiplayer. @@ -3219,8 +3271,8 @@ Character editor fixes and improvements: - A dropdown for selecting which content package to add the character to. - Allow creating a new content package during character creation. - Added hotkeys 1, 2, 3 for limb, joint, and animation modes respectively. -- Change the logic of deciding on which parameters are shown and when. In the ragdoll mode you can now -see the ragdoll, but also scale it and see the main parameters. Source rects are now longer shown on the +- Change the logic of deciding on which parameters are shown and when. In the ragdoll mode you can now +see the ragdoll, but also scale it and see the main parameters. Source rects are now longer shown on the sprite sheet if limbs mode is disabled. - Automatically select edit limbs mode when a new character is created. - Option to create multiple limbs in the character creation wizard. @@ -3229,7 +3281,7 @@ sprite sheet if limbs mode is disabled. - The spritesheet is shown by default. - Fixed deleted joints not being saved properly. - Only lock the axis for torso/head position when alt is down. -- Disable test pose if the character is not a humanoid. +- Disable test pose if the character is not a humanoid. - Clamp camera offset so that the character is always at least partially visible. --------------------------------------------------------------------------------------------------------- @@ -3240,26 +3292,26 @@ Changes: - Made husks more common. Multiplayer fixes: -- Fixed an ID mismatch problem that occasionally caused clients to get kicked out in the multiplayer +- Fixed an ID mismatch problem that occasionally caused clients to get kicked out in the multiplayer campaign, usually with an error message warning about a missing item. - Fixed servers using a local campaign save path sent by the client setupping the campaign, preventing the campaign from starting up if the path does not exist at the server's end. -- Fixed radio communication occasionally not working between characters in the multiplayer campaign. Characters -that had made it through at least one round were not able to communicate through radio with characters that +- Fixed radio communication occasionally not working between characters in the multiplayer campaign. Characters +that had made it through at least one round were not able to communicate through radio with characters that have just been spawned for the first time. -- Kick votes persist during a multiplayer session even if the client disconnects (= disconnecting and rejoining +- Kick votes persist during a multiplayer session even if the client disconnects (= disconnecting and rejoining just before you get kicked doesn't work anymore). - The server sends a "this client previously used the name xxx" message when a client rejoins with a different name. -- Fixed GameServer.UnbanPlayer passing the name to BanList in lower case even though BanList was case +- Fixed GameServer.UnbanPlayer passing the name to BanList in lower case even though BanList was case sensitive (preventing unbanning clients with the "unban" command if their name is not in lower case). -- Fixed server not saving the whitelist when it's enabled/disabled, causing the setting to revert when +- Fixed server not saving the whitelist when it's enabled/disabled, causing the setting to revert when relaunching the server. -- Fixed structure texture scale and texture offset now being reverting to default values when the +- Fixed structure texture scale and texture offset now being reverting to default values when the submarine is saved by the server, causing them to reset between campaign rounds. -- Fixed client always launching the default "DedicatedServer.exe" even if using a content package that +- Fixed client always launching the default "DedicatedServer.exe" even if using a content package that should replace the server exe. -- Servers only report content packages with files that cause multiplayer incompatibility to the master server. -Fixes servers showing up as incompatible in the server list if they have custom sub files (or other types +- Servers only report content packages with files that cause multiplayer incompatibility to the master server. +Fixes servers showing up as incompatible in the server list if they have custom sub files (or other types of files that don't cause compatibility issues) installed. - Fixed crashing when clients attempted to kick/ban players through the in-game info menu when a round was running. @@ -3270,11 +3322,11 @@ Misc fixes: - Replaced WinForms with SDL which should resolve most of the fullscreen/resolution issues. It's also now possible to change the resolution when in fullscreen without having to restart the game. - Fixes to the DXGI crashes during startup. -- Fixed Character.GetConfigFile searching for the character file from all available content packages, not -just the ones that are currently selected. Caused modded character files to affect the game even when +- Fixed Character.GetConfigFile searching for the character file from all available content packages, not +just the ones that are currently selected. Caused modded character files to affect the game even when the mod was not enabled, leading to various issues such as characters failing to equip items if the mod changes the number or type of inventory slots a character has. -- Fixed a bug in monster/item spawnpoint logic that occasionally caused perfectly valid spawnpoints to be +- Fixed a bug in monster/item spawnpoint logic that occasionally caused perfectly valid spawnpoints to be discarded when there were floating ice chunks in the level, sometimes causing monsters to spawn very far from the submarine (which often lead to the player not running into them at all). - Fixed bots not being able to retaliate when attacked with a repair tool. @@ -3282,7 +3334,7 @@ far from the submarine (which often lead to the player not running into them at - Fixed items applying their status effects twice if they're put into a container by swapping them with another item. For example, replacing a fuel rod in the reactor by dropping a new fuel rod on it caused the reactor behave as if it had two rods in it. -- Switches send out a continuous 0/1 signal that can be flipped by interacting with the switch (as opposed +- Switches send out a continuous 0/1 signal that can be flipped by interacting with the switch (as opposed to working like buttons which send out a pulse when interacted with). - Fixed an issue that occasionally caused the main menu to look distorted when launching the game on Mac. - Fixed crash after opening the file browser from the Workshop menu more than once on Linux. @@ -3290,20 +3342,20 @@ to working like buttons which send out a pulse when interacted with). - Fixed item scales not being saved when the scale is modified in the sub editor. - Fixed incorrect physicorium shell description. - Fixed server list & workshop menu not resizing properly when changing resolution. -- Fixed player input being used to determine whether a character should be holding on to ladders, causing +- Fixed player input being used to determine whether a character should be holding on to ladders, causing AI characters to let go of ladders when holding RMB. - Fixed explosion damage bypassing armor (both creature shells and wearable items). - Fixed OverrideSaveFolder and OverrideMultiplayerSaveFolder settings not being saved. - Fixed order/rept icons "twitching" when the sub moves. or -- Fixed players being able to repair submerged electrical items indefinitely. -- Fixed a "attempted to access a removed ragdoll" console error when a character wearing the health +- Fixed players being able to repair submerged electrical items indefinitely. +- Fixed a "attempted to access a removed ragdoll" console error when a character wearing the health scanner HUD is removed. - Fixed console errors when a battery's or supercapacitor's capacity is set to 0 and the interface is open. - Fixed ability to drop items into secure lockers (or other containers that require specific items) without having access to it. - Automatically move the gaps linked to doors when the door is moved (and vice versa). - Fixed items disappearing when they're dropped into a full container. -- Fixed crashing when an item that requires aim to use is used by something else than a character +- Fixed crashing when an item that requires aim to use is used by something else than a character (e.g. a status effect). - Fixed having a broken device on a sub in the sub editor and switching to Character Mode causing a crash. - Fixed the bottom of the background ice texture not rendering correctly on high resolutions. @@ -3334,28 +3386,28 @@ other jobs even when there's 3 or less players on the server). - Increased default respawn transport time to 5 minutes, decreased respawn interval to 3 minutes. - Allow muting players mid-round through the info menu. - Allow to edit the door opening/closing speeds and increase the defaults. -- Reduce the follow-up distance and require the bot to be in the same room than the player before stopping, +- Reduce the follow-up distance and require the bot to be in the same room than the player before stopping, so that bots with a follow order don't stay on the doorways when the player is trying to enter the airlocks. - Stop "breathing" deformations when the character is dead. - Disable obstructed paths when the submarine docks to another submarine/outpost. -- The location of save files can be changed in config_player.xml using the attributes "overridesavefolder" +- The location of save files can be changed in config_player.xml using the attributes "overridesavefolder" and "overridemultiplayersavefolder". Bugfixes: - Fixed crashing during startup due to faulty OpenAL installations. - Fixed inability to select the voice capture device if the name of the device contains non-latin symbols (Cyrillic or Chinese characters for example). -- Fixed a networking issue that occasionally caused clients to get kicked with a "disconnected due to +- Fixed a networking issue that occasionally caused clients to get kicked with a "disconnected due to excessive desync" error message. - Fixed fires being very hard to put out completely in multiplayer. - Fixed items not being repaired after purchasing repairs in the campaign. -- Removed outdated Launch_BarotraumaServer script from the Linux version (does not work anymore, the +- Removed outdated Launch_BarotraumaServer script from the Linux version (does not work anymore, the dedicated server should be launched by running the file called "DedicatedServer"). - Fixed server owners not being able to join their own server if whitelist is enabled and the owner is not on the whitelist. - Fixed inability to ban clients by their Steam ID using the console. - Fixed all servers showing up as "Round has not started" in the server list. -- Fixed server ignoring the max players value set in the "host server" menu and using the setting +- Fixed server ignoring the max players value set in the "host server" menu and using the setting configured in "serversettings.xml" instead. - Fixed spectators not hearing the living players' voice chat. - VOIP improvements (less crackling and pops). @@ -3363,24 +3415,24 @@ configured in "serversettings.xml" instead. an oversized crew. - Fixed occasional console errors when ending a round (causing the round end summary not to appear). - Waypoint fixes in vanilla subs. -- Disallow shooting and attacking when the cursor is over a UI element (to prevent, for example, +- Disallow shooting and attacking when the cursor is over a UI element (to prevent, for example, accidentally firing a gun when dismissing a message box). - Fixed "file not found" errors on Linux when attempting to enable a mod that defines file paths using a backslash instead of a slash. - Fixed monsters ignoring decoys. -- Fixed store menu switching back to the equipment category every time something is bought/sold in +- Fixed store menu switching back to the equipment category every time something is bought/sold in the multiplayer campaign. - Fixed "push to talk" field going outside the audio settings menu on some aspect ratios. - Fixed long server names overflowing in the server list menu. - Fixed the character saying "OrderDialogSelf.dismissed" when a player removes an order from themselves. -- Fixed subinventory slots going outside the screen when for example grabbing someone with a toolbox +- Fixed subinventory slots going outside the screen when for example grabbing someone with a toolbox in the leftmost inventory slot. - Fixed monsters sometimes spawning inside the floating ice chunks within the levels. - Fixed physicorium shells not being containable in railgun shell racks. - Fixed physicorium ammo box not being containable in coilgun ammunition shelves. - Fixed inability to spawn items in characters' inventories with the "spawnitem" console command. - Fixed health interface not focusing to the most damaged limb when closing and reopening the interface. -- Prevent welding doors shut during the tutorials (as the player doesn't have access to any tools to +- Prevent welding doors shut during the tutorials (as the player doesn't have access to any tools to reopen the door). - Fixed AI pathfinding when the path is from a submarine to another submarine. - Fixed crashing when AI crew tries to find a path out of the ruins. @@ -3396,21 +3448,21 @@ v0.9.0.4 connection. - Fixed clients occasionally failing to spawn items when playing using a different language than the server, which caused them to get kicked. -- Fixed extra cargo failing to spawn in multiplayer when playing using a different language than the server. -- Fixed legacy items failing to load if a sub is saved with a language other than English and the language +- Fixed extra cargo failing to spawn in multiplayer when playing using a different language than the server. +- Fixed legacy items failing to load if a sub is saved with a language other than English and the language then changed to something else. - Fixed excessively small password input box when connecting to servers. -- Fixed a bug that occasionally caused items to drop from the inventory when moving items between +- Fixed a bug that occasionally caused items to drop from the inventory when moving items between inventory slots in the multiplayer. - Prevent junction boxes from getting damaged due to overvoltage in the engineering tutorial. -- Fixed structures getting scaled incorrectly when cloning a structure with a non-default scale in the +- Fixed structures getting scaled incorrectly when cloning a structure with a non-default scale in the submarine editor. - Fixed occasional crashes when leaving a multiplayer session while the "cinematic" at the end of the round is still playing. - Fixed items not being moved to the humanhusk's inventory when a huskified player dies (= clothes and other gear seemed to magically disappear when the character "resurrected" as an AI husk). - Fixed clients not seeing turrets rotating at their end when another client is operating the turret. -- Fixed hitscan projectiles (revolver rounds) going through walls if the weapon is fired while its +- Fixed hitscan projectiles (revolver rounds) going through walls if the weapon is fired while its barrel is partially inside the wall. - Fixed welding tools being able to weld doors and burn characters through walls. - Fixed bots reporting leaks when there are holes in the interior walls. @@ -3450,7 +3502,7 @@ v0.9.0.2 - Playing the splash screens or tutorial videos doesn't require libvlc and libvlccore to be installed on the user's system in the Linux version anymore. -Bugfixes: +Bugfixes: - Fixed a bug that caused frequent desync kicks when playing a multiplayer monster mission. - Fixed private servers showing up in the server list. - Fixed an index out of range error in DoctorTutorial if proceeding too fast to the submarine. @@ -3485,9 +3537,9 @@ v0.9.0.1 - Fixed docking interface button not working in the multiplayer. - Fixed medical doctor tutorial crashing on languages other than English. - Fixed a bug that caused AI characters to occasionally get stuck next to stairways. -- Fixed animation and ragdoll file paths getting messed up if there are multiple content packages +- Fixed animation and ragdoll file paths getting messed up if there are multiple content packages installed that include monsters with the same name. -- Fixed crashing when a StatusEffect causes an item to be used on a target. +- Fixed crashing when a StatusEffect causes an item to be used on a target. - Fixed a gap between Remora and the drone. - Added more supplies to Remora. - Fixed watchman's dialogue getting muffled. @@ -3521,20 +3573,20 @@ Bugfixes: - Fixes crashing when attempting to use symbols such as <, > or | in the save file name. - UI layout/scale fixes on resolutions larger than 1080p. - Fixed crashing when loading the same sub twice in the sub editor. -- Fixed submarines occasionally being rendered at an incorrect position when viewing them in the sub editor +- Fixed submarines occasionally being rendered at an incorrect position when viewing them in the sub editor after they've been used in-game. - Improved the syncing of ragdolled characters. - Fixed characters not receiving impact damage when ragdolled. - Fixed characters' arms occasionally spinning around when standing still. - Fixed tutorial level generation parameters being used in some normal campaign levels (leading to extremely small levels and submarines overlapping with the outposts). -- Fixed characters running more slowly when their torso is in a different hull than the feet (for example -in Humpback's bilge). +- Fixed characters running more slowly when their torso is in a different hull than the feet (for example +in Humpback's bilge). - Fixed character's feet getting stuck to platforms when climbing ladders while holding A/D. - Fixed monsters being able to drag characters through walls. - Fixed items "vanishing" if they move directly from sub to another without going outside first. - Fixed content package hash calculation failing if the package is not enabled and contains new monster files. -- Fixed inability to enable content packages if some of the files included in the package are already in +- Fixed inability to enable content packages if some of the files included in the package are already in the game folder (which may happen, for example, if enabling a content package fails). - Fixed AllowRagdollButton settings not being synced with clients, leading to strange ragdolling behavior client-side if the server has disabled ragdolling. @@ -3587,7 +3639,7 @@ from getting attacked by a ball of overlapping crawlers. - Fixed huge lag spikes when a character tries to escape from an enemy but can't find a path away from it. - Fixed file transfer progress bars not being visible in the server lobby. - Fixed crashing when attempting to start a mission round with mission type set to None. -- Fixed ElectricalDischarger electricity effect staying visible if the item breaks or the component +- Fixed ElectricalDischarger electricity effect staying visible if the item breaks or the component is deactivated from outside (e.g. via a StatusEffect or the parent component). - Fixed specular maps being rendered on top of characters when outside the sub. - Fixed excessively bright lights around sonar flora and lava vents. @@ -3595,7 +3647,7 @@ is deactivated from outside (e.g. via a StatusEffect or the parent component). - Fixed inability to scroll through long texts in the sub editor's textboxes. - Fixed clients not being able to see other characters in spectator if they've died far away from the sub. - Fixed non-latin characters not being displayed correctly in Workshop item texts. -- Don't prevent selecting items in the sub editor when the cursor is on a wire node, because it makes it +- Don't prevent selecting items in the sub editor when the cursor is on a wire node, because it makes it very difficult (or impossible) to select small items in the wiring mode. - Fixed crashing when attempting to use the "spawnitem" command when a round is not running. @@ -3645,27 +3697,27 @@ v0.8.9.9 Additions and changes: - New control scheme: items are selected by left clicking, deselected with right click or esc, and held -items are used on devices by pressing E (e.g. when rewiring with a screwdriver or repairing something +items are used on devices by pressing E (e.g. when rewiring with a screwdriver or repairing something with a wrench). The new controls are somewhat experimental; the intention is to make them more intuitive to new players. You can still switch back to the legacy control scheme from the game settings. - Set default radio chat keybind to R and creature attack keybind to Mouse3. -- MODDERS, PLEASE NOTE: Moved crafting recipes from the fabricator xml to the xmls of the items. Makes it -possible for modders to add new craftable items without having to modify the fabricators. +- MODDERS, PLEASE NOTE: Moved crafting recipes from the fabricator xml to the xmls of the items. Makes it +possible for modders to add new craftable items without having to modify the fabricators. - Some menu layout improvements. -- Camera movement is disabled completely when an item interface is open (not just when the cursor is on +- Camera movement is disabled completely when an item interface is open (not just when the cursor is on the interface). - Option to disable the camera pan/zoom effects from the game settings. - Option to set a custom preview image for subs. - Allow aiming on ladders when not moving. - Characters play Entrance of the Gladiators on the guitar when wearing a clown mask. -- Display a warning on the status monitor when docked to an outpost ("Docked to X, undock before attempting +- Display a warning on the status monitor when docked to an outpost ("Docked to X, undock before attempting to maneuver the submarine"). - Improvements to the line of sight effect. Prevents ugly-looking artifacts in spots where two wall pieces meet. -- The server gives the "None" permissions to new clients, allowing server hosts to automatically give +- The server gives the "None" permissions to new clients, allowing server hosts to automatically give specific permissions to all clients. - Increased submarine masses to make it less easy for characters to push them around. -- Ping direction is shown on the sonar display when adjusting the direction slider even if directional ping +- Ping direction is shown on the sonar display when adjusting the direction slider even if directional ping is not enabled. - Tweaked charybdis' AI, attacks and animations. - Nuclear explosions cause radiation sickness. @@ -3678,11 +3730,11 @@ is not enabled. - Added blood particle effects when under high pressure. - Some optimization to reduce loading times. - Added a search bar to fabricators. -- Increased the range of docking port sounds and added a subtle camera shake when locking the ports to make +- Increased the range of docking port sounds and added a subtle camera shake when locking the ports to make it more noticeable when a sub docks. - Made all new medical items fabricable. -- Automatically put the currently equipped item in the inventory (no matter if it's one or two handed) when -picking up items that require two hands. +- Automatically put the currently equipped item in the inventory (no matter if it's one or two handed) when +picking up items that require two hands. - Job preferences can be edited mid-round in the info menu. - Slightly reduced the amount of oxygen characters consume from hulls. - Enemies don't attack outposts or targets inside it anymore. @@ -3692,31 +3744,31 @@ Multiplayer fixes: keep welding, honking a bike horn or whatever else they were doing until the server kills the character. - More reliable throw StatusEffect (= grenade explosion) syncing. Fixes clients not seeing explosions at their end. -- More reliable item wall attaching syncing. -- Servers don't attempt to send position updates for items that have no enabled physics body (e.g. attached +- More reliable item wall attaching syncing. +- Servers don't attempt to send position updates for items that have no enabled physics body (e.g. attached items). Fixes "received a position update for an item with no physics body" console errors when attaching items to walls. - Fixed spectate button staying visible when a round ends while a client is in the lobby. - Fixed remote characters sliding slowly to the left client-side when standing in place. -- Fixed server creating "attempted to create a network event for an item that hasn't been fully initialized +- Fixed server creating "attempted to create a network event for an item that hasn't been fully initialized yet" console errors when spawning LightComponents mid-round. - Fixes monsters flipping around way too often client-side (especially when inside the sub). Bugfixes: -- Fixed wire connections that have been done mid-round not working properly. +- Fixed wire connections that have been done mid-round not working properly. - Fixed crashing when attempting to speak as a monster in single player. - Fixed linked subs not getting docked correctly when loading a saved game. - Fixed turrets not working if they're placed inside the submarine. - Fixed calyxanide not being usable in syringe guns. - Explosive harpoons disappear after exploding. -- Emptying the "required items" field of an item in the sub editor now removes the item requirements (instead +- Emptying the "required items" field of an item in the sub editor now removes the item requirements (instead of using the default ones). - Fixed crashing if a fabricator finishes creating an item after the user has been removed (e.g. eaten). - Fixed crashing if none of the selected content packages contain location portraits suitable for the main menu. - Fixed projectiles not applying status effects on impact if they have no attack defined. - Fixed thorium rods not being usable in the reactor. -- Conditionals return a match when checking status tag inequality and the target has no status tags (e.g. -checking if a character doesn't have a StatusEffect with a "poison" tag returns true even if the character +- Conditionals return a match when checking status tag inequality and the target has no status tags (e.g. +checking if a character doesn't have a StatusEffect with a "poison" tag returns true even if the character has no active StatusEffects). - Fixed severed limbs occasionally noclipping into the submarine. - Fixed large engine emitting smoke before it becomes repairable. @@ -3726,12 +3778,12 @@ v0.8.9.8 --------------------------------------------------------------------------------------------------------- Additions and changes: -- Improved tutorial - better videos, instructional texts, objective list that suggest what you should do +- Improved tutorial - better videos, instructional texts, objective list that suggest what you should do next, option to rewatch the videos and re-read the instructions. - Overhauled charybdis (still a work in progress though). -- Automatically grab adjacent ladders when the top/bottom of the current ladder is reached. Makes moving -through docking ports a little less confusing. -- Option to configure when afflictions become visible with the health scanner by adding +- Automatically grab adjacent ladders when the top/bottom of the current ladder is reached. Makes moving +through docking ports a little less confusing. +- Option to configure when afflictions become visible with the health scanner by adding a "ShowInHealthScannerThreshold" attribute to the affliction. - Added labels next to periscopes in Humpback and Dugong. - Modified Humpback's bilge to make it easier for AI characters to fix. @@ -3744,14 +3796,14 @@ a "ShowInHealthScannerThreshold" attribute to the affliction. the wires without having to disconnect them). - Decreased structure damage done by frag grenades and made them disappear after they've exploded. - Batteries output charge values as integers. -- Made damaged junction boxes less sensitive to overvoltage. Nearly broken junction boxes were barely able -to handle any overvoltage, leading to chain reaction where one junction box breaking causes the grid to be +- Made damaged junction boxes less sensitive to overvoltage. Nearly broken junction boxes were barely able +to handle any overvoltage, leading to chain reaction where one junction box breaking causes the grid to be overloaded, and the rest of the boxes start taking damage at an increasing speed. - Reactors don't cool down when underwater anymore. -- Removed minimum conditions from battery deconstruction output (= deconstructing an empty battery still +- Removed minimum conditions from battery deconstruction output (= deconstructing an empty battery still gives the materials used to craft the battery). - Made a bunch of ItemContainer UI panels larger. -- Items can be dragged and dropped directly from the inventory into containers without having to select +- Items can be dragged and dropped directly from the inventory into containers without having to select the container first. - Plants can be picked up from the environment without any tools. - Added more help texts to highlighted items ("[E] Interact", "[E] Climb"...) @@ -3765,24 +3817,24 @@ excessive amounts of network events. - Fixed clients being unable to start a campaign using a submarine that's not in the default Submarine folder at the server's side. - Fixed loading submarine files and campaign saves occasionally failing when running multiple instances -of the game from the same install location (for example, a dedicated server executable and a client +of the game from the same install location (for example, a dedicated server executable and a client executable). -- Don't transfer files through the network when sending them to the owner of the server (i.e. a client +- Don't transfer files through the network when sending them to the owner of the server (i.e. a client hosting directly from the main executable). - Fixed fires and water occasionally getting out of sync between a client using the fire/water console commands and the server. - Fixed clients disconnecting with an "unknown object header" error if they fail to read a network event (when they should instead report the error to the server and wait for a message that contains a more descriptive error). -- Campaign fix: clear missions from locations that change their type, and all adjacent locations. Not -clearing them caused missions to still be available when they logically shouldn't be (e.g. a transport +- Campaign fix: clear missions from locations that change their type, and all adjacent locations. Not +clearing them caused missions to still be available when they logically shouldn't be (e.g. a transport mission from an uninhabited location to another) and syncing issues in multiplayer. - Disable campaign start button if a round is already running when joining. -- Fixed clients being unable to end campaign rounds at all if the sub isn't at the start/end outpost +- Fixed clients being unable to end campaign rounds at all if the sub isn't at the start/end outpost (regardless if they have the permission to end the round or not). -- Fixed campaign characters still being displayed in the server lobby after the game mode has been +- Fixed campaign characters still being displayed in the server lobby after the game mode has been changed to something else. -- Fixed items in the characters inventory always starting at 100% condition client-side even if they had +- Fixed items in the characters inventory always starting at 100% condition client-side even if they had deteriorated during the previous round. - Fixed LevelResource (mineral, plant, etc) deattach timers not being synced with clients. - AI characters can take out excess fuel rods from the reactor when needed. @@ -3791,8 +3843,8 @@ deteriorated during the previous round. Bugfixes: - Fixed almost all items using default repair duration values (10 seconds with high skills, 100 seconds with low skills) instead of the ones configured in the item XMLs. -- Nuclear shells and nuclear depth charges disappear after they've exploded. -- Fixed "trying to add a dead character to crewmanager" errors when attempting to revive a character +- Nuclear shells and nuclear depth charges disappear after they've exploded. +- Fixed "trying to add a dead character to crewmanager" errors when attempting to revive a character killed by some other affliction than internal damage, bleeding or burns. - Take the position of a sub's docking port into account when determining where to place outposts. Previously the outposts were simply placed midway between the adjacent walls, which occasionally caused @@ -3800,7 +3852,7 @@ problems with submarines whose docking port is close to the bow or tail. - Fixed a bug in relay components that caused a bunch of issues in power grids that utilize relays: Relays would receive the full amount of power from the grid regardless of the load of the devices connected to the power_out connection, causing unnecessary overloads and fires. -- Fixed batteries being able to draw power through relay components that are connected directly to +- Fixed batteries being able to draw power through relay components that are connected directly to a power source, even if the relay isn't on. - Don't allow steering the sub with WASD when a textbox is selected. - Use the SpriteColor of the item when drawing the moving parts of turrets and doors. @@ -3811,16 +3863,16 @@ something inside the sub. - Fixed characters always being created in the default folder in the character editor. - Monsters don't target doors/hatches at the exterior of the sub when inside or inner doors when outside. - Don't display disabled limbs on sonar (i.e. severed limbs that have "faded out"). -- Close the save/load dialogs when leaving the sub editor. Otherwise they'll still be visible when +- Close the save/load dialogs when leaving the sub editor. Otherwise they'll still be visible when re-entering the editor, and saving at that point will overwrite the previously loaded sub with an empty one. -- Removing an item after it's been combined doesn't trigger the OnBroken StatusEffects (e.g. combining two +- Removing an item after it's been combined doesn't trigger the OnBroken StatusEffects (e.g. combining two half-full flash powder jars doesn't cause them to explode). - Fixed welding tools and plasma cutters not hitting targets if the barrel is inside the target (e.g. if trying to weld a completely broken wall with the cutter partially inside the wall). - Fixed very small mineral colliders that made them extremely hard to hit with the plasma cutter. - Fixed items with no sprite crashing the game (now they just cause a console error). -- Don't allow autointeracting with contained items (e.g. picking up an ammunition box from a loader) -if another item is currently selected. Makes it less likely for players to accidentally pick up items +- Don't allow autointeracting with contained items (e.g. picking up an ammunition box from a loader) +if another item is currently selected. Makes it less likely for players to accidentally pick up items from containers when they deselect another item. - Fixed characters not letting go of the character they're grabbing when the health interface is closed by clicking outside the window. @@ -3838,7 +3890,7 @@ Additions and changes: - Clients communicate syncing errors to the server, and the server logs a more descriptive error about what went wrong. Should make it easier to diagnose disconnection issues from now on. - Ending a multiplayer campaign round by talking to watchman doesn't require any special permissions. -- Server automatically ends rounds if there have been no players alive in 60 seconds and respawning +- Server automatically ends rounds if there have been no players alive in 60 seconds and respawning is not allowed during the round. - Added a button for resetting an entity's properties to the default values to the sub editor. - Updated handheld sonar UI graphics. @@ -3848,11 +3900,11 @@ Bugfixes: - Fixed a networking bug that caused the server to send item state changes to the clients before sending a message about the item being spawned. For example, spawning any item with a LightComponent would always cause clients to get disconnected. -- Changes to the way the clients are put in sync with the server when joining mid-round. Should make it +- Changes to the way the clients are put in sync with the server when joining mid-round. Should make it less likely for clients to get disconnected immediately after starting a round. -- StatusEffects only apply non-limb-specific afflictions to one limb even if targeting the whole character. -Fixes drugs like fentanyl and morphine being way too harmful due to the oxygen loss affliction being -applied once per every limb. +- StatusEffects only apply non-limb-specific afflictions to one limb even if targeting the whole character. +Fixes drugs like fentanyl and morphine being way too harmful due to the oxygen loss affliction being +applied once per every limb. - Fixed TargetItemComponentName not working in StatusEffect conditionals (making it impossible to create conditionals that target a specific component of an item). - Made all of the new medical items combinable and usable in a syringe gun (assuming the drug is in a syringe). @@ -3864,12 +3916,12 @@ conditionals that target a specific component of an item). - Fixed flares not activating by left clicking. - Fixed affliction icons flickering rapidly in the health interface and above the health bar if their strength is fluctuating around the threshold where the icon becomes visible. -- Fixed dedicated server crashing when typing in more text than can fit on one line. -- Fixed enemies "fleeing" after they have been shot. There was a steering issue when they targeted characters +- Fixed dedicated server crashing when typing in more text than can fit on one line. +- Fixed enemies "fleeing" after they have been shot. There was a steering issue when they targeted characters inside the sub while being outside. - Fixed Hammerhead attack causing warping. - Fixed incorrect submarine and level seed in server logs when playing campaign mode. -- Hide the start button from the campaign UI if the client doesn't have the permission to manage +- Hide the start button from the campaign UI if the client doesn't have the permission to manage the campaign or rounds. --------------------------------------------------------------------------------------------------------- @@ -3889,8 +3941,8 @@ themselves alive and less likely to get stuck. - New signal items (divide, multiply, subtract, memory, equals, greater than, color, xor). - Option to adjust microphone volume in multiplayer. - Added a console commands for changing the gender and race of the character. -- More intuitive BrokenSprite condition logic: a BrokenSprite with a MaxCondition of 50 will start -fading in at 50 (and be fully visible when the condition drops to 0 or down to the MaxCondition of +- More intuitive BrokenSprite condition logic: a BrokenSprite with a MaxCondition of 50 will start +fading in at 50 (and be fully visible when the condition drops to 0 or down to the MaxCondition of the next BrokenSprite). - Added Mirror X/Y buttons to editing HUDs and tooltips that tell about the keyboard shortcuts. @@ -3905,15 +3957,15 @@ knowing that you'd dropped it. - Fixed "play yourself" always toggling to true when a round ends. - Fixed missing item names in the extra cargo menu. - Fixed traitor rounds failing to start if the server is not hosted by a client. -- Fixed console command aliases not being taken into account in GameClient.HasConsoleCommandPermission -(meaning that the client needed a permission for each name variant of a command, making it impossible +- Fixed console command aliases not being taken into account in GameClient.HasConsoleCommandPermission +(meaning that the client needed a permission for each name variant of a command, making it impossible to for example use "fixwalls" instead of "fixhulls"). -- Made the "control" console command usable to clients. -- Show the "ready to start" tickbox in the server lobby even if the client has the permission to start +- Made the "control" console command usable to clients. +- Show the "ready to start" tickbox in the server lobby even if the client has the permission to start the round. - Fixed server lobby screen not showing the names of the submarines the client doesn't have. - Fixed inability to select the respawn shuttle as a client host. -- Fixed VoipCapture creating new "could not start voice capture" popups constantly if there's no +- Fixed VoipCapture creating new "could not start voice capture" popups constantly if there's no suitable capture device. - Fixed crashing when starting a round if a submarine name contains underscores. - Fixed clients console errors when attempting to modify the properties of an ItemComponent in-game @@ -3924,29 +3976,29 @@ suitable capture device. Misc bugfixes: - Audio fixes (less snap, crackle and pop). - Fixed particle "jitter" when the submarine was moving fast. -- Fixed damage modifiers affecting all afflictions if they use affliction types instead of affliction -identifiers. +- Fixed damage modifiers affecting all afflictions if they use affliction types instead of affliction +identifiers. - Fixed end round vote text going outside the screen if there's a 2-digit amount of votes. - Fixed StatusEffects only applying afflictions to one limb even if the target is "Character" instead of "Limb". - Disable audio instead of crashing if no audio device is found. - Fixed item interfaces getting repositioned every frame when the editing HUD is open. -- Fixed held items clipping with the sleeves of the character (e.g. when holding a revolver while an +- Fixed held items clipping with the sleeves of the character (e.g. when holding a revolver while an uniform is equipped). - Fixed being able to levitate by spamming the ragdoll button. - Fixed dead characters draining oxygen tanks inside diving suits/masks. - Fixed reactor gauges getting messed up if the optimal fission rate is more than 100% (which may happen if the power consumption is larger than what the reactor can generate). - Fixed mud raptors not having an inventory (nor lootable items). -- Fixed inability to interact with any items when aim assist is set to 0%. -- Fixed info panel flickering out and Tab getting "inverted" (= info panel shown when tab is not being held) +- Fixed inability to interact with any items when aim assist is set to 0%. +- Fixed info panel flickering out and Tab getting "inverted" (= info panel shown when tab is not being held) when selecting crew members in the panel. - Fixed characters arms occasionally getting stuck above their shoulders. -- Fixed wire nodes occasionally being created at the wrong end of a wire (e.g. when moving a wire between -connections in a connection panel, the wire stretched from the device at the other end of the wire to +- Fixed wire nodes occasionally being created at the wrong end of a wire (e.g. when moving a wire between +connections in a connection panel, the wire stretched from the device at the other end of the wire to the device that's being rewired). Misc: -- Changed the way arguments are given to the "setclientcharacter" command (no semicolon to separate the +- Changed the way arguments are given to the "setclientcharacter" command (no semicolon to separate the names, quotation marks have to be used for multi-word names just like with any other command). - Show the amount of credits in the crew tab of the campaign menu. - Don't spawn new monsters if docked to the start outpost or within 50 meters of the start/end of the level. @@ -3964,7 +4016,7 @@ Bugfixes: used to fetch the texts from the language files instead of the actual texts). - Fixed AI orders that target a specific item (such as the order to power up the reactor) not working in multiplayer. -- Fixed crashes when attempting to use voice capture or change voice capture settings when there are no +- Fixed crashes when attempting to use voice capture or change voice capture settings when there are no suitable capture devices available. - Fixed clients not being notified when an AI character shuts down the reactor. - Fixed deconstructors staying active without power in multiplayer. @@ -3992,13 +4044,13 @@ two separate server applications. - Option to randomize your job preferences in the server lobby. - Fixed a server timing issue that occasionally caused the server to kick clients due to desync when a round starts. - Fixed occasional server-side "maximum packet size exceeded" errors. -- Require the players to either dock with the ending outpost or to get the sub close and enter the outpost before +- Require the players to either dock with the ending outpost or to get the sub close and enter the outpost before automatically ending the round. Bugfixes: - Fixed crashing if the round ends while the health window is open. -- Fixed incorrect item panel positioning in the crew command interface when the sub is docked to something. -- Fixed crashing when an incompatible content package is selected in config.xml or if the content package +- Fixed incorrect item panel positioning in the crew command interface when the sub is docked to something. +- Fixed crashing when an incompatible content package is selected in config.xml or if the content package cannot be found. - Fixed screen distortion effects on Linux. - Fixed non-character key input on Linux (arrow keys, tab, etc). @@ -4012,21 +4064,21 @@ slots in uniforms. - Fixed AI not reloading coilguns if an empty box of ammunition is inserted in the loader. - Fixed incorrect deusizine scale. - Fixed turret light toggle not doing anything. -- Fixed character skills that aren't defined in the job xml never increasing, resulting in all jobs except +- Fixed character skills that aren't defined in the job xml never increasing, resulting in all jobs except the captain always having a helm skill of 0. - Fixed flashlight & scooter light cones being "clipped". - Fixed StatusEffects bypassing limb damage modifiers. - Fixed waypoints not getting connected between docking ports on some subs. - Fixed target identifiers being bypassed when a StatusEffect is set to target nearby items or characters. -- Fixed the "insufficient skills to use the item" text popping up if a character doesn't have sufficient -skills to operate one of the item's components, even if the component was not interacted with (e.g. captains -got a warning about not being able to use the connection panel of a nav terminal, even if they didn't select +- Fixed the "insufficient skills to use the item" text popping up if a character doesn't have sufficient +skills to operate one of the item's components, even if the component was not interacted with (e.g. captains +got a warning about not being able to use the connection panel of a nav terminal, even if they didn't select the connection panel). Steam Workshop: - Update installed workshop items automatically on startup. - Allow adding submarines to workshop items with the "add file" dialog. -- If creating an update for a workshop item that's currently installed, use the installed version instead +- If creating an update for a workshop item that's currently installed, use the installed version instead of the one downloaded from the workshop. Additions: @@ -4038,20 +4090,20 @@ Additions: - Display linked hulls as one room on the status monitor. - Tons of new sound effects. - Display the controlled character in the crew interface. -- Option to "give orders" to the character you're controlling. In single player it can be useful if you want -the controlled character to keep doing something when switching to another one, in the multiplayer it can be +- Option to "give orders" to the character you're controlling. In single player it can be useful if you want +the controlled character to keep doing something when switching to another one, in the multiplayer it can be used to let others know what you're doing. - Added a weak spot to Moloch's bladder. -- Baby Moloch, doo doo doo doo doo doo +- Baby Moloch, doo doo doo doo doo doo - Added damage particles to Mud Raptors and Molochs. -- Added "minimum velocity" property to to motion sensors. Allows making sensors that, for example, keep a door open +- Added "minimum velocity" property to to motion sensors. Allows making sensors that, for example, keep a door open when a character is standing in the doorway. - Option to choose whether to use AND/OR logic in StatusEffects with multiple conditionals. Defaults to AND. -- Added a 1 second "cooldown" to water detector state switches to prevent alarms from toggling on and off constantly +- Added a 1 second "cooldown" to water detector state switches to prevent alarms from toggling on and off constantly when the water level is fluctuating around the position of the detector. - Added scram option (reactor shutdown) to the nav consoles in the vanilla subs. - Support for binding Mouse4, Mouse5 and MouseWheel. -- Made Hammerhead and Mudraptor attracted to light. +- Made Hammerhead and Mudraptor attracted to light. - New husk sprite (still WIP). Misc: @@ -4061,17 +4113,17 @@ repaired. - Miscellaneous optimization. - Removed the info button from the top-left corner - the info menu is now opened with TAB. - Changed default chat/radio keybinds to T and Y. -- Welding tools repair all the walls within the range of the raycast, not just the first wall the raycast hits. +- Welding tools repair all the walls within the range of the raycast, not just the first wall the raycast hits. Makes it easier to repair overlapping and multi-layered walls. - Decreased the range of passive sonar - previously there was often no reason to use the active sonar because the passive mode showed the area around the sub so clearly. -- Health scanner shows all active afflictions (not just those that are visible in the health interface). +- Health scanner shows all active afflictions (not just those that are visible in the health interface). Allows detecting afflictions at an earlier stage, making the item much more useful. - Nerfed the structure damage done by Molochs and Crawlers. - Reduced creature HP across the board. - Increased the amount of minerals in levels. - Increased flare burn time, making them more useful as path markers during exploration of ruins. -- RepairTool damage is configured using StatusEffects and Afflictions instead of the "limbfixamount" attribute +- RepairTool damage is configured using StatusEffects and Afflictions instead of the "limbfixamount" attribute that always does burn damage. - Made headsets craftable. - Battery output doesn't start dropping until the charge is below 10%. @@ -4100,17 +4152,17 @@ mission configuration despite the item being removed. - Made coilgun ammunition boxes craftable and purchaseable, coilgun bolts cannot be purchased anymore. - Fixed AI-controlled husk not spawning when a huskified player dies. - Fixed AI crew occasionally going outside to fix leaks. -- Fixed server failing to sync clients who join the server after a character has been removed during +- Fixed server failing to sync clients who join the server after a character has been removed during the round (e.g. eaten, turned into a husk). - Fixed server-side console errors when clients attempt to use a fabricator. - Display Steam authentication errors in the server logs. - Fixed status effects with a ReduceAffliction value of 0 freezing the game. - Fixed sliders not moving in the battery/supercapacitor interface when an AI character is operating it. -- Fixed chatbox being deselected in the net lobby when receiving a lobby update from the server (i.e. +- Fixed chatbox being deselected in the net lobby when receiving a lobby update from the server (i.e. whenever the server host changes any setting). - Fixed OnBroken status effects firing in the submarine editor when an item's condition is set to zero (for example, reactors exploding and breaking all the nearby walls). -- Fixed file number being added to the file extension of debug console log files ("file123.txt (2)" +- Fixed file number being added to the file extension of debug console log files ("file123.txt (2)" instead of "file123 (2).txt"). - Fixed battery positioning in charging docks. - Fixed crashing when ending a single player round while a character is outside the sub. @@ -4118,21 +4170,21 @@ instead of "file123 (2).txt"). - Fixed fire sounds persisting in menus. - Fixed the layout of the extra cargo menu in server settings. - Fixed depth charges disappearing from loaders when interacting them with both hand slots full. -- Fixed StatusEffects not being able to target item components. Caused doors to be impossible to weld -and most likely other issues with item StatusEffects as well. +- Fixed StatusEffects not being able to target item components. Caused doors to be impossible to weld +and most likely other issues with item StatusEffects as well. - Artifacts spawn in artifact holders again. - Fixes to "attempted to move pulljoint extremely far" errors which occasionally caused severe problems in syncing characters' positions. - Fixed a bug that occasionally caused monsters to spawn very close to the submarine in monster missions. -- Fixed servers occasionally starting the round multiple times when automatically starting the game via -autorestart or clients being ready. +- Fixed servers occasionally starting the round multiple times when automatically starting the game via +autorestart or clients being ready. - Fixed up-to-date content packages being reported as incompatible in the Steam workshop menu. - Changed the default radio chat hotkey to T. - Fixed the line of sight effect not working on ruins when looking at them from inside a sub. - Fixed fabricator allowing new items to be created when the output is not empty, resulting in wasted materials. - Fixed servers reporting incorrect player counts in the server list. - Fixed order messages not being visible in single player if the character issuing the order has no headset. -- Fixed riot shields retaining their pushing ability even when the user is stunned or unconscious. +- Fixed riot shields retaining their pushing ability even when the user is stunned or unconscious. - Fixed rubber ducks not floating like a good duck should. - Prevent locations from being generated too close to each other in the campaign map. - Fixed battery and supercapacitor charges not staying in sync between the server and clients. @@ -4140,7 +4192,7 @@ autorestart or clients being ready. - Fixed non-downloaded workshop items showing zero as the file size. - Fixed spectate button staying disabled if starting a round fails (due to a missing sub file for example). - Fixed crashing when teleporting characters from a submarine to ruins in multiplayer. -- Fixed automatic temperature control setting turbine output above 100 if the power consumption is higher +- Fixed automatic temperature control setting turbine output above 100 if the power consumption is higher than what the reactor can generate. Caused "failed to write an event for the entity" errors in multiplayer. - Fixed AI characters attempting to treat dead characters. @@ -4162,7 +4214,7 @@ with fires inside them. - Fixed the husk appendage not appearing on huskified humans. - Fixed order/report messages being flagged as spam way too easily, causing frequent spam kicks. - Fixed sliders buttons being invisible while pressed in device interfaces. -- Fixed an item being spawned in the submarine editor when selecting an item from the menu while another +- Fixed an item being spawned in the submarine editor when selecting an item from the menu while another one is already selected. - Fixed submarine colliders not taking into account the body offsets of the wall structures, causing some items outside the submarine's walls to be impossible to interact with (the most noticeable being the @@ -4178,39 +4230,39 @@ button that opens Orca's airlock from the outside). v0.8.9.1 (closed alpha) --------------------------------------------------------------------------------------------------------- -Too many changes to list here. :) We've been working on this update for a year now, with a team of about +Too many changes to list here. :) We've been working on this update for a year now, with a team of about a dozen people (as opposed to a couple of devs working on it on their free time). -Almost every aspect of the game has been improved to some extent - some polished a bit, some went through +Almost every aspect of the game has been improved to some extent - some polished a bit, some went through a more major overhaul and many things are completely new. This is by no means a complete list, but here's some of the new things: - A full graphics overhaul: almost all of the sprites has been polished or completely remade. - Improved random event system that tries to keep the overall difficulty of the game at certain level, -delaying additional monster spawns if there's already lots of things going on, or spawning more when +delaying additional monster spawns if there's already lots of things going on, or spawning more when there's a more quiet moment. - Improved difficulty system: now the difficulty level has a much more noticeable effect on the gameplay. - General difficulty balancing all across the board: we've tried to make the difficulty curve more approachable to new players while still keeping things challenging for more experienced players on higher difficulty levels. - More varied levels, environmental hazards. -- A new more detailed health system with things such as limb-specific injuries, addictions, overdoses, +- A new more detailed health system with things such as limb-specific injuries, addictions, overdoses, mental issues... The system is also highly moddable, and makes it much easier to implement things such as hunger mechanics, more varied poisons or stat-boosting items. - Completely redesigned in-game HUD (the inventory, crew command interface, chat, etc). - Redesigned crafting system. - Minerals scattered across the level (can be used for crafting). -- A command/report system that can be used to communicate with your crew more effectively (in both single +- A command/report system that can be used to communicate with your crew more effectively (in both single player and multiplayer). - Tons of additions to alien ruins (traps, puzzles, non-flooded rooms). - Improved AI (both the crew AI and the enemy AIs). -- NPC dialog (including random chatter and context-specific lines that make it easier to keep track of +- NPC dialog (including random chatter and context-specific lines that make it easier to keep track of what the crew is doing). - Most of the device interfaces have been redesigned to make them easier to use (and nicer to look at!). - Many additions to the campaign mode (still a work in progress though). -- Overhauled the skill system: now every character can generally do anything (repair devices, fabricate +- Overhauled the skill system: now every character can generally do anything (repair devices, fabricate new items, apply medical treatments), but characters with higher skill levels will do things more efficiently. -- Skill progression in the campaign mode: characters' skills gradually increase, making them more valuable with +- Skill progression in the campaign mode: characters' skills gradually increase, making them more valuable with each completed round. - New music composed specifically for the game. - Overhauled audio. @@ -4233,9 +4285,9 @@ v0.8.2.3 - Fixed a bunch of bugs that were causing "attempted to apply an invalid force/impulse to a physics body" errors. - Fixed a bunch of bugs that were causing "attempted to move a pulljoint extremely far" errors. -- Fixed DebugConsole selecting non-command lines if up/down is pressed when there are no commands in the -console. -- Fixed inventory syncing not working on the controlled character's inventory if the character is +- Fixed DebugConsole selecting non-command lines if up/down is pressed when there are no commands in the +console. +- Fixed inventory syncing not working on the controlled character's inventory if the character is unconscious or wearing handcuffs. - Verify that the launched exe belongs to the currently selected content package when starting up the game. - Fixed console messages that have been created before initializing the debug console not being present @@ -4254,18 +4306,18 @@ causing the camera not to follow the character and preventing the player from gi - Fixed a few ragdoll animation bugs that caused "attempted to move pulljoint anchor extremely far" errors. - Fixed AI characters (most often mantises) being able to attack through walls. - Fixed alien ruins occasionally overlapping with each other or being above the upper boundary of the level. -- Docking ports automatically stretch the hulls between them to cover the area between the docked subs. -Otherwise there may be areas uncovered by hulls if the docking port is positioned slightly outside the -extents of the submarine's hulls, causing characters to implode or get thrown back when they try to pass +- Docking ports automatically stretch the hulls between them to cover the area between the docked subs. +Otherwise there may be areas uncovered by hulls if the docking port is positioned slightly outside the +extents of the submarine's hulls, causing characters to implode or get thrown back when they try to pass from sub to another. -- Fixed client-side docking ports creating duplicate bodies on doors, causing characters to collide with +- Fixed client-side docking ports creating duplicate bodies on doors, causing characters to collide with an invisible door when trying to move between docked subs (until the server forces them through it). -- Fixed characters occasionally getting teleported outside the sub for a few frames when moving between +- Fixed characters occasionally getting teleported outside the sub for a few frames when moving between docked subs. - Fixed status effects that deplete oxygen affecting characters that don't need air to breathe. - More error logging to diagnose syncing errors. - Melee weapons can only hit one character per swing (makes stun batons and medical syringes less OP). -- Option to make RegEx component only send a signal when it receives a signal (not continuously according +- Option to make RegEx component only send a signal when it receives a signal (not continuously according to the last received signal). - Added FalseOutput property to RegEx components. @@ -4278,10 +4330,10 @@ v0.8.2.1 another. - Fixed dragged characters staying floating mid-air after they've been dragged up/down ladders. - Attempt to fix items occasionally dropping client-side when moving them from an inventory to another. -- Fixed incorrect rotation of welding tools and other 2-handed items that are held in one hand when not +- Fixed incorrect rotation of welding tools and other 2-handed items that are held in one hand when not aiming. The items were rotated according to the left hand, but positioned on the right hand. - Spaces and exclamation marks are allowed in client names by default. -- Hitscan weapons like the revolver can hit targets outside the sub when firing from the inside and +- Hitscan weapons like the revolver can hit targets outside the sub when firing from the inside and vice versa. - Fixed crashing when attempting to clone linked submarines in the sub editor. - Fixed crashing when attempting to clone items with non-default required items. @@ -4295,56 +4347,56 @@ v0.8.2.0 --------------------------------------------------------------------------------------------------------- Networking additions: - - Added a server setting for selecting which symbols are allowed in client names (see + - Added a server setting for selecting which symbols are allowed in client names (see AllowedClientNameChars in the server settings file). - Custom servers can modify all editable item properties mid-round, not just in-game editable ones. - Clients can be given access to server logs. - Respawn durations can be changed mid-round. - Servers have the option to disable the disguise feature. - Increased midround syncing timeout. - + Misc changes: - Levels are mirrored when travelling through the backwards in the campaign mode. - Added colliders to railguns (so they cannot go through walls or enemy subs anymore). - - Melee weapons can hit multiple targets on one swing. Fixes weapons occasionally not hitting + - Melee weapons can hit multiple targets on one swing. Fixes weapons occasionally not hitting the target in tight spaces due to touching the ceiling/walls first. - - The voltage required for a PowerTransfer item to take damage and the probability for a fire can be + - The voltage required for a PowerTransfer item to take damage and the probability for a fire can be configured in the item xmls. - Docking ports and hatches aren't damaged by excess voltage. - Added more color variants of wires. - Characters point the harpoon gun down when not aiming. - Added parameter autocompletion to the kill command. - - Added a property that can be used to lock connection panels but still keep the panel rewireable + - Added a property that can be used to lock connection panels but still keep the panel rewireable in the submarine editor. - Items outside the sub cannot be deattached from walls. - - Fabricators show the list of required items even if the character does not have the skills to craft + - Fabricators show the list of required items even if the character does not have the skills to craft the item. Networking bugfixes: - - Fixed file transfers failing if the client disconnects during an active transfer, rejoins and + - Fixed file transfers failing if the client disconnects during an active transfer, rejoins and attempts to receive the same file. - - Fixed a bug in door syncing that caused the door states to differ between the server and clients + - Fixed a bug in door syncing that caused the door states to differ between the server and clients in some subs with more complex door wiring setups. - - Fixed clients being able to spam kick votes (duplicate votes were not counted but caused unnecessary + - Fixed clients being able to spam kick votes (duplicate votes were not counted but caused unnecessary chat messages to be sent). - - Fixed item conditions occasionally not matching exactly between the server and clients, causing - issues such as not clients not being able to fabricate items due to the condition being slightly + - Fixed item conditions occasionally not matching exactly between the server and clients, causing + issues such as not clients not being able to fabricate items due to the condition being slightly below the minimum condition at their end. Bugfixes: - Added a workaround to a MonoGame bug that makes the screen turn white when alt-tabbing out of fullscreen. - Fixed docking ports flooding for no reason in some custom subs. - Fixed LightComponents staying active on broken items. - - Fixed railguns and depth charge tubes being directly usable by characters (= they could be launched + - Fixed railguns and depth charge tubes being directly usable by characters (= they could be launched simply by selecting them and left clicking, without the need to use a railgun controller). - Fixed items salvaged from ruins not being saved in the campaign mode. - - Fixed LOS effect being brighter than the ambient light in some of the darker levels, causing + - Fixed LOS effect being brighter than the ambient light in some of the darker levels, causing the player to see obstructed areas better than unobstructed ones. - Fixed severed limbs staying disabled when a dismembered character is revived using console commands. - Fixed characters holding non-aimable two-handed items such as railgun shells in one hand when aiming. - Fixed stereo sounds not being loaded correctly. - Fixed modified sprite colors not working correctly on worn items. - - Fixed modified maximum recharge speeds of PowerContainers resetting to the default value after + - Fixed modified maximum recharge speeds of PowerContainers resetting to the default value after saving and reloading. - Fixed handcuffed players being able to perform CPR and grab/drag bodies. - Fixed diving suit's damage modifiers being bypassed if the character gets hit in the waist. @@ -4355,13 +4407,13 @@ v0.8.1.12 --------------------------------------------------------------------------------------------------------- - Fixed connectionpanel syncing (wires dropping client-side during rewiring). -- Characters stay alive for 30 seconds after a client disconnects, and if the client rejoins during that -time they regain control of the character. +- Characters stay alive for 30 seconds after a client disconnects, and if the client rejoins during that +time they regain control of the character. - Changing the sprite color of an item also affects the color when the item is being worn. - Cloned items keep the value of the "required items" field of the original item. - Fixed crashes caused by gaps that are not connected to anything. - Removed the unused and non-functional monitor item. -- Made medical and toxic cabinets waterproof (= potassium and other water-sensitive items inside them +- Made medical and toxic cabinets waterproof (= potassium and other water-sensitive items inside them are not affected by water). - Fixed docking ports causing flooding in some custom subs. - Fixed medical items with an immediate effect (such as calyxanide) not working when a player uses them @@ -4382,22 +4434,22 @@ Networking fixes: clients to get kicked due to desync. - Fixed client-side error messages when respawning without a respawn shuttle. - Fixed some issues in inventory and connection panel syncing when joining mid-round. - - Fixed fabricated items always appearing to be in full condition client-side (e.g. oxygen tanks which + - Fixed fabricated items always appearing to be in full condition client-side (e.g. oxygen tanks which should be empty after being fabricated). - - Fixed attachable items dropping on the ground client-side when deattaching them (but still staying + - Fixed attachable items dropping on the ground client-side when deattaching them (but still staying in the inventory of the character detaching them). - Fixed items occasionally dropping instead of being moved to another inventory client-side. - The "traitorlist" command is usable by clients who have the permission to use it. Misc bugfixes: - - Fixed characters occasionally going inside/through obstacle when leaving a submarine that's right + - Fixed characters occasionally going inside/through obstacle when leaving a submarine that's right next to another submarine or a level wall. - More physics error checks and logging. - Railgun controllers can be used over wifi components. - Characters attach items to the walls at the position of their hand, not at the center of the body. - Wifi components can't communicate with the enemy sub in combat missions. - - Fixed the previously selected location staying selected but start button staying disabled when - returning to the lobby screen in the single player campaign. Made it impossible to progress without + - Fixed the previously selected location staying selected but start button staying disabled when + returning to the lobby screen in the single player campaign. Made it impossible to progress without restarting if there were no other selectable locations. - Fixed holdable components reverting their RequiredItems back to the prefab values during loading. - Fixed wall-attached sections of a wire not rendering when the item is being rewired outside the sub. @@ -4407,11 +4459,11 @@ Misc bugfixes: v0.8.1.10 --------------------------------------------------------------------------------------------------------- -- Fixed bugs in wall hole creation logic and docking port syncing which caused entity ID mismatches and +- Fixed bugs in wall hole creation logic and docking port syncing which caused entity ID mismatches and "unknown object header" errors. - Fixed errors when attempting to buy too many items of a given type to fit in one container. - Fixed crashing when attempting to buy items that don't spawn in a container. -- Fixed crashing when attempting to generate hulls with the "autohull" command when there are no walls +- Fixed crashing when attempting to generate hulls with the "autohull" command when there are no walls or doors in the sub. - Fixed docking ports creating duplicate hulls and gaps during loading. - Fixed missions resetting to the initial ones when loading a campaign. @@ -4424,14 +4476,14 @@ v0.8.1.9 --------------------------------------------------------------------------------------------------------- - Fixed a bug in docking port syncing that caused entity ID mismatches and "unknown object header" errors. -- Fixed a bug that occasionally caused entity ID mismatches and "unknown object header" errors when +- Fixed a bug that occasionally caused entity ID mismatches and "unknown object header" errors when a respawn shuttle got damaged before returning back to the starting location. - Fixed error messages when attempting to use the console commands "\" or "\n". - Fixed clients not syncing the position of their controlled character with the server when dead/unconscious. - Fixed swimming ragdolls "dropping down" if the server freezes them due to a connection error. - Fixed excessive "attempted to apply invalid velocity to a physics body" console errors. - Fixed crashing when selecting a level seed that has no background portrait defined. -- Fixed crashing if the player clicks yes on the "download sub from the server" prompt after +- Fixed crashing if the player clicks yes on the "download sub from the server" prompt after returning to the main menu. --------------------------------------------------------------------------------------------------------- @@ -4468,7 +4520,7 @@ them (despite the server allowing voting if the client has had a character earli use the position of the client's cursor. - Fixed crashing if a wire is used by a statuseffect (for example if a detonator tries to trigger a wire contained inside it). -- Fixed GameAnalytics being stopped if the dedicated server is restarted with the "restart" console command.- +- Fixed GameAnalytics being stopped if the dedicated server is restarted with the "restart" console command.- - Fixed wiring items outside the submarine. - Fixed chatbox discarding the second chat message instead of the first one when the maximum number of chat messages is reached. @@ -4489,7 +4541,7 @@ select whether you want to send the information or not. - Devices outside the submarine can be rewired in-game (not just in the sub editor). - Fixed a crash caused by vision obstruction logic. - Fixed clients being unable to give non-permanent or range bans. -- Clients are allowed to vote to end the round if they have spawned at some point during the round, +- Clients are allowed to vote to end the round if they have spawned at some point during the round, even if the character they controlled doesn't exist anymore. - Dedicated servers can give clients the permission to use console commands that aren't available in for dedicated server (e.g. los, lights, control) @@ -4498,7 +4550,7 @@ for dedicated server (e.g. los, lights, control) the command instead of the host's character. - Spawnitem can be used to spawn items in the inventory of a specific character. - Fixed explosions with an EMP value only damaging reactors (when they should only ignore reactors). -- Fire can only explode oxygen tanks that are >25% full (otherwise the condition of the tank just drops +- Fire can only explode oxygen tanks that are >25% full (otherwise the condition of the tank just drops to 0). Prevents infinite explosions when an oxygen generator is on fire with oxygen tanks inside. - Fixed projectiles with a damage range of 0 not applying their structuredamage value to structures. - Items with a physics body can be used as pumps, so now it's possible to make portable items that remove @@ -4518,12 +4570,12 @@ v0.8.1.3 --------------------------------------------------------------------------------------------------------- - Fixed server-side crashes during job assignment if a client hasn't sent any job preferences. -- Fixed crashing if the selected respawn shuttle doesn't have a navigation terminal or any other item +- Fixed crashing if the selected respawn shuttle doesn't have a navigation terminal or any other item with a Steering component. -- Fixed InWater status effects triggering when an item is fabricated, causing issues such as +- Fixed InWater status effects triggering when an item is fabricated, causing issues such as water-sensitive items to breaking/exploding immediately after being fabricated. - Fixed motion sensors sending out signals even if the output is set to nothing. -- Fixed crashing when a round starts if the sub has been saved while a fabricator was running. +- Fixed crashing when a round starts if the sub has been saved while a fabricator was running. - Fixed explosives not detonating inside railgun shells. - Fixed characters spawning inside the respawn shuttle if no suitable spawnpoint is found inside the main submarine. @@ -4547,7 +4599,7 @@ v0.8.1.2 v0.8.1.1 --------------------------------------------------------------------------------------------------------- -- Fixed explosives going off when a character holds them in their hand and left clicks, causing a crash +- Fixed explosives going off when a character holds them in their hand and left clicks, causing a crash if done in the submarine editor. --------------------------------------------------------------------------------------------------------- @@ -4556,17 +4608,17 @@ v0.8.1.0 Items: - Added searchlights. - - Explosives, chemicals and medical items disappear when their condition falls to 0 + - Explosives, chemicals and medical items disappear when their condition falls to 0 (i.e. when they're fully used). - Railguns cannot be fired when not being aimed. - - Removed the need for batteries in diving suits. The light stays on as long as the suit is worn + - Removed the need for batteries in diving suits. The light stays on as long as the suit is worn by a living character. - Junction boxes only take damage underwater when they're powered up. Bugfixes: - - Fixed a bug that occasionally caused some characters to not be removed at the end of a round, causing - various bugs and crashes on successive rounds (the most common ones being server-side crashes and - constant "attempted to access a potentially removed ragdoll" console errors). + - Fixed a bug that occasionally caused some characters to not be removed at the end of a round, causing + various bugs and crashes on successive rounds (the most common ones being server-side crashes and + constant "attempted to access a potentially removed ragdoll" console errors). - Fixed camera shake continuing indefinitely if a character falls unconscious due to impact damage. - Fixed item removal by right clicking not being synced with clients. - Fixed being able to gain karma by welding fixed walls. @@ -4578,14 +4630,14 @@ Bugfixes: - Fixed the EndRound music clip occasionally looping forever after a round ends. - Fixed player-controlled creatures being able to damage themselves. - Fixed repair tools causing damage to the user regardless of the character's skills. - - Attempt to fix characters occasionally getting launched out of the sub at lightspeed when the sub + - Attempt to fix characters occasionally getting launched out of the sub at lightspeed when the sub crashes into something. - Fixed StatusEffects not working on child ItemComponents. - Wearables apply OnWearing StatusEffects in all the components of an item, not just the Wearable component. - Fixed Equals/NotEquals conditional comparisons. Misc additions: - - Added console commands for giving the clients ranks, showing their current permissions and + - Added console commands for giving the clients ranks, showing their current permissions and giving/revoking the permission to use specific console commands. - Option to set an automatic ban duration for vote kicked players. - Option to log debug console output into a text file. @@ -4608,7 +4660,7 @@ when entities were removed mid-round. - Fixed clients getting desynced if the server ends a campaign and starts a new one. - Fixed the "campaign view" button staying visible in the server lobby after the campaign has ended. - Fixed message boxes appearing behind the campaign setup menu in the server lobby. -- Fixed round summaries always showing the game over text in multiplayer if the submarine didn't progress +- Fixed round summaries always showing the game over text in multiplayer if the submarine didn't progress to the next location. - Fixed missing spark particles when welding/cutting a wall. - Fixed plasma cutters not affecting hatches, alien doors or duct blocks. @@ -4618,20 +4670,20 @@ instead of a limb. - Fixed crashing when attempting to perform CPR on a headless character or AS a headless character. - Fixed attachable items being deattachable with the select key instead of the use key. - Fixed clients being unable to open doors with crowbars. -- Fixed items attached mid-round by other clients or the host being impossible to interact with +- Fixed items attached mid-round by other clients or the host being impossible to interact with and occasionally being attached to an incorrect position. - Fixed batteries not getting recharged in charging docks. - Fixed monsters being able to spawn under the ocean floor in levels where the floor is high up. - Fixed effects of the medical items not being stackable, meaning that successive usages of a medicine did not have an effect until the effect of the first dose has worn off. -- Fixed items in itemcontainers (e.g. shells in railgun loaders, batteries in recharging docks) always +- Fixed items in itemcontainers (e.g. shells in railgun loaders, batteries in recharging docks) always being rendered with the default sprite color. - Fixed crashing when clicking the "refreshing server list" text in the server list menu. - Fixed dedicated servers not resetting votes when a round ends. - Fixed interaction areas of some items being incorrect in the dedicated server. - Fixed the removal of items that get deleted after being used not being synced. -- Ladder waypoint generation fix: waypoints are not just placed at the top and bottom of the ladders, -but above every platform along the ladders (-> waypoints work correctly on ladders spanning through +- Ladder waypoint generation fix: waypoints are not just placed at the top and bottom of the ladders, +but above every platform along the ladders (-> waypoints work correctly on ladders spanning through multiple floors). --------------------------------------------------------------------------------------------------------- @@ -4663,13 +4715,13 @@ v0.8.0.2 - Sound configuration files are included in content packages. - Fixed "file not found" errors when a character is wearing footwear with no configured footstep sounds. - MODDERS PLEASE NOTE: hit sounds on limbs and wearable items must now use tags instead of direct paths -to the sound file. New sound files and tags can be added by editing the sound configuration files. +to the sound file. New sound files and tags can be added by editing the sound configuration files. --------------------------------------------------------------------------------------------------------- v0.8.0.1 --------------------------------------------------------------------------------------------------------- -- Fixed crashing when creating items with a PowerTransfer component mid-round (e.g. when fabricating +- Fixed crashing when creating items with a PowerTransfer component mid-round (e.g. when fabricating a relay component) - Fixed dead/spectator chatting in multiplayer. - Chatboxes can be deselected with the chat hotkey again. @@ -4688,9 +4740,9 @@ v0.8.0.0 - Made ambient lighting much darker and added a subtle glow around the player. - Made partially damaged walls leak much more slowly. -- Nerfed wall damage. Crawlers, mantises, threshers and coelanths now take much more time to tear through +- Nerfed wall damage. Crawlers, mantises, threshers and coelanths now take much more time to tear through the hull and collisions with the level cause less damage. -- Moved a bunch of hard-coded texts to an xml file "Content/Texts.xml". Full translation support is still +- Moved a bunch of hard-coded texts to an xml file "Content/Texts.xml". Full translation support is still in progress, but now it should be possible to translate most of the in-game texts without recompiling the game. - Optimized rendering. - More accurate endworm attack hit detection. @@ -4735,7 +4787,7 @@ Items: - Made more items craftable and deconstructable. - Added sprites for broken doors, hatches and junction boxes. - Health Scanner HUD displays causes of death. - - Health Scanner HUD only shows the status of the visible character that the cursor is closest to + - Health Scanner HUD only shows the status of the visible character that the cursor is closest to prevent multiple characters from cluttering the screen - Added "smallitem" tag to revolver rounds (can be placed in cabinets and pockets now). - Option to determine which types of targets a projectile can stick to. @@ -4744,29 +4796,29 @@ Items: - Characters can hold flashlights in their mouths. - Added an inventory slot for ID cards. - If a character is wearing an item that obscures their face, the game will either hide their name or - show the name that's in the ID card the character is wearing. + show the name that's in the ID card the character is wearing. - The owner of an ID card is stated in the description of the card. - - Fabricating items requires the ingredients to have a specific minimum condition (can't use + - Fabricating items requires the ingredients to have a specific minimum condition (can't use already-used consumables to craft something). - Deconstructing an item that's not in a full condition may prevent some deconstruction products from appearing. - Optimized electricity/signal logic. - Grenades can be triggered by detonators. - Bought cargo spawns in containers instead of being scattered across the floor. - - Players can't use other items when a railgun controller is selected. Prevents accidentally firing + - Players can't use other items when a railgun controller is selected. Prevents accidentally firing weapons or hitting people with something while using the railguns. - Item editing menus display color values as 0-255 instead of 0-1. - - Reactor temperature has to be critical for 30 seconds before the reactor explodes, giving the crew - more time to deal with griefers or incompetent reactor operators. The reactors also have an output + - Reactor temperature has to be critical for 30 seconds before the reactor explodes, giving the crew + more time to deal with griefers or incompetent reactor operators. The reactors also have an output connection that sends out a signal when the temperature is critical. Multiplayer additions: - - Added an optional "karma system". Harming other players, damaging structures and blowing up the reactor - reduces karma, while repairing things causes it to increase. A too low karma level prevents the players + - Added an optional "karma system". Harming other players, damaging structures and blowing up the reactor + reduces karma, while repairing things causes it to increase. A too low karma level prevents the players from choosing specific jobs - Clients can be given permission to use specific console commands. - Client permission presets: the clients can be assigned as moderators or admins which have specific pre-configured permissions. New presets can be added by editing Data/permissionpresets.xml. - - Option to have more than one traitor. Traitors also now get a set of code words that can be used to + - Option to have more than one traitor. Traitors also now get a set of code words that can be used to secretly identify other traitors. - Added an option to make players spawn directly in the main submarine instead of the respawn shuttle. - Keybinds are disabled when the chatbox is active. Now it's possible to use normal letter/number keys @@ -4777,24 +4829,24 @@ Multiplayer additions: - Cutting and repairing walls is included in server logs. Multiplayer bugfixes: - - Fixed clients getting disconnected due to desync when a new monster is spawned mid-round by + - Fixed clients getting disconnected due to desync when a new monster is spawned mid-round by a repeating monster event. - Fixed modified clients being able to chat while unconscious due to the lack of server-side checks. - Fixed modified clients being able to disconnect locked wires due to the lack of server-side checks. - Fixed chat messages being assigned to the wrong sender when their bodies have been eaten. - Fixed crashing when setting a server filter while the game is refreshing the server list. - - Fixed wires not being dropped server-side when a player drops a connected wire without dragging it + - Fixed wires not being dropped server-side when a player drops a connected wire without dragging it to their inventory first. - Fixed 5th server being impossible to select in the server list. - Improvements to character position syncing. - Re-enabled logic for preventing players from using visually similar names. - - Fixed client-side null exception when the client is in the lobby and a round ends with the mission + - Fixed client-side null exception when the client is in the lobby and a round ends with the mission successfully completed. - Fixed clients being able to votekick/kick/ban themselves in the server lobby. - Fixed "selected mode" and "mission type" settings not being saved. - Level seed randomization can be toggled on and off via the debug console. - Fixed dedicated server not randomizing the submarine or game mode even if randomization is enabled. - - Lighting is forced back on when a client starts a round (-> can't disable lighting by using the + - Lighting is forced back on when a client starts a round (-> can't disable lighting by using the console command before joining a server). Bugfixes: @@ -4823,11 +4875,11 @@ Bugfixes: - Fixed being able to use a wrench on multiple items at the same time. - Fixed double-clicking items in corpses putting them in their hands instead of your own inventory. - Fixed some level seeds generating a tiny enclosed cave that makes it impossible to reach the destination. - - Fixed opened and broken doors being ignored during waypoint generation, causing waypoint connections + - Fixed opened and broken doors being ignored during waypoint generation, causing waypoint connections to go through doors which prevented AI characters from opening them. - Fixed modified structure colors not being cloned. - Fixed modified wall colors only being visible in the submarine editor. - - Fixed items being dropped when attempting to place them in an itemcontainer slot that's on a normal + - Fixed items being dropped when attempting to place them in an itemcontainer slot that's on a normal inventory slot. - Fixed stack overflow exceptions caused by signal loops between junction boxes. - Fixed submarine editor crashing when attempting to use illegal characters in the filename. @@ -4850,7 +4902,7 @@ v0.7.0.1 - Removed serverconfig.xml (the dedicated server now uses the same config file as the normal game). - Updated the vanilla content package to version 0.7. - Fixed entity linking in the submarine editor. -- Fixed railgun HUD crashing the game if the railgun is linked to an item that does not have an +- Fixed railgun HUD crashing the game if the railgun is linked to an item that does not have an ItemContainer component (i.e. any item that can't contain other items). - Fixed exceptions when the player dies in the tutorial. - Fixed the start popup saying the host is the target if the host has been selected as the traitor. @@ -4890,7 +4942,7 @@ Particles: Bugfixes: - Fixed the "DXGI_ERROR_DEVICE_REMOVED" crashes on specific GPUs when the loading reaches 80%. - Fixed crashes when projectiles stuck to items on dedicated server. - - Fixed a bunch of bugs that caused crashes when a character was removed mid-round (for example when + - Fixed a bunch of bugs that caused crashes when a character was removed mid-round (for example when a character turns into a husk). - Fixed a bug that occasionally caused swimming creatures to flip around constantly. - Fixed a bug that caused creatures to be able to sever limb joints that shouldn't be possible to sever, @@ -4903,7 +4955,7 @@ Bugfixes: - Fixed AI characters never making any sounds in multiplayer. - Fixed inability to rebind keys to Mouse2 via the settings menu. - Fixed destroyed doors being impossible to repair. - - Fixed creatures seeking towards an incorrect position when trying to eat something (causing larger + - Fixed creatures seeking towards an incorrect position when trying to eat something (causing larger creatures like threshers and coelanths to swim around the target without ever reaching it). Items: @@ -4914,7 +4966,7 @@ Items: - Added sprites for destroyed doors/hatches. - Added a HUD that shows the charge of the supercapacitors and the amount of shells left when using a railgun. - - Inventory slots are be highlighted even if the cursor is within the empty space between them. Now + - Inventory slots are be highlighted even if the cursor is within the empty space between them. Now items can't be accidentally dropped by releasing the mouse button between the slots. - Sounds for picking up and dropping items. - Medical scanner displays husk infections. @@ -4922,7 +4974,7 @@ Items: - Optimizations and minor visual changes to the sonar display. - Support for hitscan projectiles. - Fixed ranged weapons launching projectiles with an incorrect rotation. - - The spread of ranged weapons can be adjusted (separate values for normal spread and spread when + - The spread of ranged weapons can be adjusted (separate values for normal spread and spread when the item is being used by a character with an inadequate skill level). - Heavier harpoon gun recoil and impulse when the spear hits something. @@ -4935,7 +4987,7 @@ Items: Misc: - Flowing water pushes characters around much more heavily. - Warning texts when water pressure is increasing to dangerous levels and when running out of oxygen. - - Made the damage range of limb attacks configurable (instead of having it always be half of the distance + - Made the damage range of limb attacks configurable (instead of having it always be half of the distance at which the attack activates) and tweaked the damage ranges of all the creature attacks. - Option to filter the server list based on a bunch of criteria. - Added a radio chat hotkey. @@ -4951,18 +5003,18 @@ v0.6.1.4 - Fixed reactors staying operational after the fuel rods run out. - Fixed spawning items at the cursor via the debug console. - Fixed job assignment logic causing crashes if the maximum amount of players per job has been reached -on the top 3 preferences of a client (which can happen on modded servers that have several jobs with a +on the top 3 preferences of a client (which can happen on modded servers that have several jobs with a player limit). - Fixed errors during job assignment if a client or the host is controlling a non-human character. - The server log menu remembers the state of the filters when toggling it. - Fixed level generation errors in some specific seeds that caused the game to create ruin walls with a negative width/height (example seed: cBLgZ2im). -- Fixed a submarine position syncing issue that caused erratic physics behavior on some specific +- Fixed a submarine position syncing issue that caused erratic physics behavior on some specific multi-part subs, because the game only moved one part of the sub and the parts docked to it, not taking into account that more parts may be docked to the docked parts. - Handcuffed AI characters can't climb ladders. - Fixed crashing when a huskified human is killed by an explosion. -- Added some GPU info to the crash reports and extra debug logging to hopefully diagnose the +- Added some GPU info to the crash reports and extra debug logging to hopefully diagnose the SharpDXExceptions during startup. --------------------------------------------------------------------------------------------------------- @@ -4971,7 +5023,7 @@ v0.6.1.3 - Fixed hulls not being rendered in the submarine editor. - Crouching is synced between the server and the clients. -- Plasma cutters and welding tools ignore platforms and stairs - placing a platform on a wall doesn't +- Plasma cutters and welding tools ignore platforms and stairs - placing a platform on a wall doesn't prevent welding/cutting the wall anymore. - Using the SetClientCharacter console command forces the client's line of sight effect back on. - Fixed null reference exceptions when syncing docking ports that haven't been docked to anything. @@ -4985,7 +5037,7 @@ v0.6.1.2 - Dedicated servers can use autorestart. - Fixed dedicated servers ignoring armor. - Fixed console messages not appearing in the crash reports if the game crashes during loading. -- Attachable items (buttons, electrical components, etc) are automatically attached to walls when placed +- Attachable items (buttons, electrical components, etc) are automatically attached to walls when placed in the submarine editor. - Fixed crashing if no matching character is found when using the SetClientCharacter console command. - Fixed wires disconnecting from a connection panel when a player moves any of the wires. @@ -5016,13 +5068,13 @@ Multiplayer: - Job preferences don't reset when quitting the game. - Added MessageBox chat message type. Allows custom servers to display custom message boxes to the clients. - Logging when a character throws an item. - - Logging which items are contained inside items characters use on themselves (e.g. which meds are + - Logging which items are contained inside items characters use on themselves (e.g. which meds are inside a medical syringe). - Logging which type of projectile was launched from a railgun and which items were contained inside it. - - More descriptive wiring logging: the logs don't list all the wires in a connection panel but only + - More descriptive wiring logging: the logs don't list all the wires in a connection panel but only the changes players do to the wiring. -Monsters: +Monsters: - Some creatures can hunt for smaller creatures (including humans) and eat them. - Tweaked enemy AI to make their attacks less likely to miss. - Some creatures flee when their health decreases below a specific threshold. @@ -5034,26 +5086,26 @@ Monsters: - The camera zooms further out when controlling a large non-humanoid character. Misc: - - Improved item interaction logic: highlighting items is more precise, with items directly under + - Improved item interaction logic: highlighting items is more precise, with items directly under the cursor taking priority. - Characters can be dismembered by creatures and explosions. - New blood particles. - Blood, explosion and fire decals. - - Added an artifact that attracts creatures. - - Detached buttons and electrical components can be picked up just like any other item, instead of + - Added an artifact that attracts creatures. + - Detached buttons and electrical components can be picked up just like any other item, instead of having to use a wrench and wait for the item to "detach". - Wires can't be connected to detached items. - Debug commands can be autocompleted using tab. - Added a debug command for creating explosions. - -Bugfixes: + +Bugfixes: - Fixed "loading was interrupted due to an error" crashes on startup. - Fixed "destination array was not long enough" errors in AddToGUIUpdateList. - Fixed error messages when a character gets stunned for over 60 seconds in multiplayer. - Characters don't consume oxygen from rooms when wearing a diving mask or a diving suit. - Fixed occasionally seeing through walls when swimming outside a submarine. - Fixed crashes during map generation caused by very large wall cells near the entrance of the level. - - When highlighting a wire in a connection panel, the physical wire and the items connected to it are + - When highlighting a wire in a connection panel, the physical wire and the items connected to it are highlighted. - Fixed crashing when selecting a sonar monitor in a submarine with no hulls. - Fixed submarine/shuttle lists occasionally appearing empty after joining a server. @@ -5070,7 +5122,7 @@ v0.6.0.2 - Fixed a bug that caused non-interactable checkboxes to always appear unchecked. - Skill level syncing fix: the syncing isn't dependent on the order of the characters skills anymore. - IP addresses are included in all login error messages and the errors are also logged to the debug console. -- Servers end rounds if all players are either dead or unconscious when autorestart is on (instead of +- Servers end rounds if all players are either dead or unconscious when autorestart is on (instead of waiting for all players to die) - Fixed nuclear shells and depth charges exploding immediately when launched. - Fixed a bug that prevented any broken items from being repaired in the single player. @@ -5083,8 +5135,8 @@ v0.6.0.1 - Readded spam filter. - Servers log the automatic temperature control setting of nuclear reactors. -- If a client fails to start a round (due to a missing sub file or an error, for example), their character -is automatically killed. This prevents situations where a team can't win a combat mission due to a +- If a client fails to start a round (due to a missing sub file or an error, for example), their character +is automatically killed. This prevents situations where a team can't win a combat mission due to a disabled, invisible character in the opposing team. - Fixed clients occasionally displaying the "crew has been defeated" message immediately after a combat mission starts. @@ -5117,7 +5169,7 @@ UI: - multi-line chat messages don't overlap Items: - - passive sonar: when not active, the sonar shows nearby sources of sound and a faint outline of the + - passive sonar: when not active, the sonar shows nearby sources of sound and a faint outline of the structures around them. Now it's much easier to monitor how much noise the submarine is making and to hide from enemies. - new sonar visuals @@ -5127,7 +5179,7 @@ Items: - buttons created in fabricators work now Submarine editor: - - items/structures that have been copy-pasted from another submarine don't disappear when saving and + - items/structures that have been copy-pasted from another submarine don't disappear when saving and loading the sub - fixed crashes when attempting to load a submarine with no walls - placing a resizable structure with a height/width of zero is not allowed @@ -5145,7 +5197,7 @@ Misc: - heal and revive commands can also be used on other characters than the controlled one - fixed fires occasionally causing incorrect sound clips to loop continuously - AI controlled crew members are better at avoiding hazards such as water and fire - - swimming animation fix: characters don't swim with their legs extended up over their shoulders + - swimming animation fix: characters don't swim with their legs extended up over their shoulders after a sharp turn --------------------------------------------------------------------------------------------------------- @@ -5190,7 +5242,7 @@ v0.5.4.3 - a new enemy - some new sound effects by Omniary - some structure-specific damage sounds -- the size of docked subs is taken into account when determining the spawn position of the sub (large +- the size of docked subs is taken into account when determining the spawn position of the sub (large multi-part subs shouldn't spawn inside walls anymore) - explosion damage is calculated based on the distance to the closest surface of a limb instead of the center position of the limb (i.e. large monsters can be damaged by smaller explosions) @@ -5210,13 +5262,13 @@ v0.5.4.2 --------------------------------------------------------------------------------------------------------- - fixed crashes when removing nodes from a wire (i.e. right clicking with a wire equipped) -- fixed inventory not being drawn in the correct position if switching to a character who's been +- fixed inventory not being drawn in the correct position if switching to a character who's been dragged/grabbed by some other character - fixed wires becoming disconnected when copypasting them - wire nodes can't be moved when connecting wires to a connection panel - fixed repeating crash messageboxes if the game fails to resolve a SharpDX exception on startup - fixed crashing when switching to wiring mode while editing some value of an item -- fixed keyboard focus staying in textboxes after the textbox has been hidden (for example, +- fixed keyboard focus staying in textboxes after the textbox has been hidden (for example, the input fields in the submarine saving prompt) - fixed error message spam if a docking port is linked to another port in the same sub - submarine lists in the editor, main menu and server menu are updated when new subs are saved/received @@ -5225,7 +5277,7 @@ the input fields in the submarine saving prompt) - the size of the docked subs is taken into account when generating the level - fixed autorestart timer not resetting at the clients' end if the server fails to start a shift and resets the timer -- docked subs are forced to correct positions during loading (subs won't get stuck inside each other +- docked subs are forced to correct positions during loading (subs won't get stuck inside each other even if the submarines are slightly overlapping in the editor) - all sounds are paused when switching to submarine editor @@ -5243,19 +5295,19 @@ Bugfixes: - fixed a bug that occasionally caused crashing when the game happens to generate a very small level Sub editor: - - structures/items that are behind something else can be selected using a listbox that appears - when hovering the cursor over them - - wires have to be selected by clicking before any of the points can be moved (makes it possible + - structures/items that are behind something else can be selected using a listbox that appears + when hovering the cursor over them + - wires have to be selected by clicking before any of the points can be moved (makes it possible to move the correct wire even if it's overlapping with other wires) - the selected wire is renderer over all structures - points can be added to wires by clicking while holding ctrl - disabled music Misc: - - some rendering optimization + - some rendering optimization - pathfinding and waypoint generation improvements - made mantises more aggressive - - water flows more slowly through partially damaged walls + - water flows more slowly through partially damaged walls --------------------------------------------------------------------------------------------------------- v0.5.4.0 @@ -5264,12 +5316,12 @@ v0.5.4.0 Submarine editor: - copy, paste and cut functionality - items/structures can be copied by holding ctrl while dragging - - it's possible to move a wire by moving both items it's connected to (without having to move each + - it's possible to move a wire by moving both items it's connected to (without having to move each individual point of the wire separately) - - "hull volume helper" which makes it easier to select a suitable ballast tank size and + - "hull volume helper" which makes it easier to select a suitable ballast tank size and NeutralBallastLevel setting in the navigation terminal - equipped items are removed when switching from wiring mode to character mode or vice versa - - no need to wait when deattaching items from the walls with a wrench + - no need to wait when deattaching items from the walls with a wrench Bugfixes: @@ -5277,18 +5329,18 @@ Bugfixes: - UI elements (buttons, textboxes, etc) can't be clicked through each other anymore - fixed a bug that caused crashes when deattaching items from walls - fixed a game-crashing particle bug - - fixed respawned characters getting assigned to a different team than the rest of the characters + - fixed respawned characters getting assigned to a different team than the rest of the characters (causing them to be displayed separately in the crew menu) - pathfinding/autopilot fixes Misc: - - server hosts can give players special privileges (kick, ban, end round) + - server hosts can give players special privileges (kick, ban, end round) - saving the contents of the server info box and the traitor setting - - changes to battery logic: they can now be used to cover the entire power consumption of the + - changes to battery logic: they can now be used to cover the entire power consumption of the electrical grid (assuming their maximum output is high enough) - - added "artifact holders" to alien ruins (which can also be used for turning artifacts into power + - added "artifact holders" to alien ruins (which can also be used for turning artifacts into power sources if installed in a sub) - - changes to character collider behavior: crouching changes the size of the collider and it's + - changes to character collider behavior: crouching changes the size of the collider and it's easier to step over small obstacles @@ -5308,7 +5360,7 @@ v0.5.3.3 - fixed a bug that caused crashes after a husk-infected player died - disabled the "zoom effect" when under pressure as a huskified human -- only a limited number of messages are kept in the debug console (prevents performance issues if large +- only a limited number of messages are kept in the debug console (prevents performance issues if large amounts of messages are added) - some item and electricity logic optimization - fixed "sprite tigerthresher not found" errors in the Linux version @@ -5346,7 +5398,7 @@ Changes to ragdoll movement/animation logic: - characters are less likely to take impact damage by stumbling in stairs - (+ makes working on the new improved netcode much easier) - ladders can be slid down by holding the sprint key - + Submarine Editor: - zoom now works relative to the mouse's position rather than the center of the screen - fixed selection rectangle not being visible when dragging from bottom right to top left @@ -5358,7 +5410,7 @@ Items: - a "glow effect" when moving items between inventory slots - option to select which location the autopilot should navigate to - fabricator UI shows item descriptions and items that can't be fabricated are grayed out - + Bugfixes: - attempt to fix "DXGI_ERROR_NOT_CURRENTLY_AVAILABLE" errors on startup - fixed water flow sounds taking up all the audio channels and preventing other sounds from playing @@ -5370,13 +5422,13 @@ Bugfixes: - waypoint generation and pathfinding bugfixes Misc: - - improved line of sight effect (instead of a solid black "fog of war", a faint image of the + - improved line of sight effect (instead of a solid black "fog of war", a faint image of the surrounding rooms can be seen through walls) - less ambient light, and it gets darker when diving deeper - - a hull-specific ambient light system: light sources increase the amount of light inside rooms, + - a hull-specific ambient light system: light sources increase the amount of light inside rooms, preventing shadows from looking unnaturally dark in fully lit submarines - option to disable vsync - - added a near-indestructible alien ruin wall variant - breaking through the walls with a railgun + - added a near-indestructible alien ruin wall variant - breaking through the walls with a railgun or a plasma cutter is not always an option anymore - added a parallax effect to the particles floating in the ocean @@ -5398,7 +5450,7 @@ Improved MiniMap (now called "Status Monitor"): - the single player map shows which locations have been visited and the passageways that have been used - minor visual improvements to the single player campaign menus - huskification bugfixes -- oxygen isn't distributed through gaps or vents that are underwater (i.e. air pockets can form when the +- oxygen isn't distributed through gaps or vents that are underwater (i.e. air pockets can form when the sub is flooding) - molochs (or other large creatures) can't push the sub around as easily anymore @@ -5419,7 +5471,7 @@ v0.5.1.2 --------------------------------------------------------------------------------------------------------- - hacked clients can't join a full server or change the name of their character anymore -- option to choose which character to control using the "control" command when there are multiple +- option to choose which character to control using the "control" command when there are multiple characters/creatures with the same name - a console command for spawning items - the server logs show who sent each chat message @@ -5471,7 +5523,7 @@ v0.5.0.3 v0.5.0.2 --------------------------------------------------------------------------------------------------------- -- more server-side sanity checks to prevent (desynced or hacking) players from doing things their +- more server-side sanity checks to prevent (desynced or hacking) players from doing things their characters shouldn't be able to do - fixed collision issues at docking ports (such as shooting up in the air when trying to drop down into a docked shuttle while shuttle hatch is closed) @@ -5515,12 +5567,12 @@ Items: - changes to the logic that determines which item is being highlighted - now it's much easier to select specific items in cramped subs - highlighted items glow (so it's easier to see which item you're targeting in the dark) - - fixed an electricity bug that sometimes caused parts of the grid to not carry any power after + - fixed an electricity bug that sometimes caused parts of the grid to not carry any power after a junction box has been broken and repaired - option to choose the output of a signal check component when the signal doesn't match - fixed fire extinquishers - item search bar in the submarine editor - - fixed cargo items spawning in incorrect positions (which occasionally caused some serious problems + - fixed cargo items spawning in incorrect positions (which occasionally caused some serious problems if the item happened to be a crate full of nitroglycerin) - flares burn longer - fixed flashes from explosions/sparks/flares occasionally ''staying on'' @@ -5674,7 +5726,7 @@ v0.4.0.0 DOCTORS: - medical doctors (can fabricate various drugs/chemicals and give CPR to unconscious characters) - - changes to the dying logic: characters will be unconscious when their health or oxygen goes below 0, + - changes to the dying logic: characters will be unconscious when their health or oxygen goes below 0, and die when it drops to -100 - medical syringes can be used on other characters - any chemicals can be inserted in medical syringes @@ -5685,7 +5737,7 @@ Items: - junction boxes, sonar monitors, navigation terminals and engines break if they're underwater long enough - reactor cools down if it's underwater (multiple fuel rods are required to bring the temperature back up) - forces are applied to items (not just characters) when the submarine hits something - - changes to the logic for distributing oxygen through vents: the oxygen generator pushes more oxygen + - changes to the logic for distributing oxygen through vents: the oxygen generator pushes more oxygen to larger rooms instead of dividing the oxygen output equally between vents - autopilot bugfixes @@ -5701,7 +5753,7 @@ Items: - a bunch of new sprites Multiplayer: - - fixed a bug that caused the server to resend a ton of messages to a client who's been temporarily + - fixed a bug that caused the server to resend a ton of messages to a client who's been temporarily disconnected, causing syncing issues to every player - fixed syncing issues related to items breaking (eg junction boxes being broken only for some players) - fixed dead monsters occasionally ''teleporting'' inside the sub in multiplayer @@ -5757,16 +5809,16 @@ v0.3.5.0 - items float and can be moved around by flowing water - wiring mode which makes wiring more convenient in the editor - networking bugfixes and improvements -- changes to the logic that determines how far the monsters can see/hear the submarine from - now it's +- changes to the logic that determines how far the monsters can see/hear the submarine from - now it's possible to evade some monsters by turning off noisy devices and/or stopping the submarine -- invisible entities (items inside cabinets, hulls/gaps when they've been hidden) can't be highlighted +- invisible entities (items inside cabinets, hulls/gaps when they've been hidden) can't be highlighted or selected in the editor - fixed monster/item spawnpoints being placed in unreachable locations - relay and delay components - fixed lights not being positioned correctly on moving items - added a ''set_color'' connection to light components - ladders outside the sub can be climbed -- changes to drowning/suffocation logic: amount of oxygen drops at a fixed rate instead of effects +- changes to drowning/suffocation logic: amount of oxygen drops at a fixed rate instead of effects "stacking" (e.g. when wearing a diving suit with no oxygen tank in a room with low oxygen) - fixed projectiles not colliding with the submarine when shot from the outside @@ -5782,7 +5834,7 @@ v0.3.4.2 v0.3.4.1 --------------------------------------------------------------------------------------------------------- -- fixed a major bug in the networking code, which caused the server to incorrectly determine the order +- fixed a major bug in the networking code, which caused the server to incorrectly determine the order of messages received from different clients and discard valid messages - fixed levels with the same seed appearing different between the Linux and Windows versions - creatures spawned using the console are synced with clients @@ -5896,8 +5948,8 @@ a round v0.3.2.0 --------------------------------------------------------------------------------------------------------- -- server logs -- server admins have the option to send messages only to dead players and spectators (/d [message]) or +- server logs +- server admins have the option to send messages only to dead players and spectators (/d [message]) or to one specific player (/name [message]) - more reliable door syncing - railgun syncing bugfixes @@ -5944,7 +5996,7 @@ v0.3.1.2 v0.3.1.1 --------------------------------------------------------------------------------------------------------- -- fixed a major bug that caused item/monster ID mismatches between the server and the clients, which +- fixed a major bug that caused item/monster ID mismatches between the server and the clients, which accounted for many of the monster/inventory/item syncing issues - improved player position syncing @@ -5980,7 +6032,7 @@ v0.3.0.4 - fixed "submarine not found" errors which occurred in multiplayer if the filename didn't match the name of the submarine - fixed new structures not lining up with existing ones if switching to editor while a round is running -- fixed a bug in shadow rendering which caused memory leaks +- fixed a bug in shadow rendering which caused memory leaks - the autoupdater only checks the Content folder when deleting files that don't belong to the latest version (i.e. the autoupdater won't delete your mods as long as they aren't saved in the Content folder) - molochs and endworms are immune to bleeding! @@ -5990,9 +6042,9 @@ v0.3.0.3 --------------------------------------------------------------------------------------------------------- - fixed selecting stairs and items outside the sub in editor -- fixed crashing when pressing the ''start'' button while no route is chosen in single player +- fixed crashing when pressing the ''start'' button while no route is chosen in single player - fixed fire syncing -- fixed another bug that crashed the game if in the lobby when a round ends +- fixed another bug that crashed the game if in the lobby when a round ends - camera keeps moving with the sub when typing into chatbox in spectator mode --------------------------------------------------------------------------------------------------------- @@ -6016,7 +6068,7 @@ v0.3.0.1 - fixed a bug that made it impossible to fix broken walls after saving and reloading - fixed crashing when trying to place ladders when no submarine has been loaded - trying to generate waypoints for an empty sub won't crash the game anymore -- when opening the crew commander menu for the first time, there's a text notifying about the hotkey for +- when opening the crew commander menu for the first time, there's a text notifying about the hotkey for opening/closing the menu @@ -6053,7 +6105,7 @@ Items: Submarines: - a new sub, Nehalennia - the collider of the submarine now matches the shape of the hull - - the airlock pumps in each sub are set to pump water out instead of just turning the pump on when pressing + - the airlock pumps in each sub are set to pump water out instead of just turning the pump on when pressing the button outside the airlock Submarine editor: @@ -6061,7 +6113,7 @@ Submarine editor: - tickboxes for hiding hulls, gaps, waypoints and links between items - a list of the most recently used items/structures - placed wires are much easier to move around - - more accurate staircase selecting (the ''bounding box'' of the staircase won't prevent selecting items that + - more accurate staircase selecting (the ''bounding box'' of the staircase won't prevent selecting items that are behind it anymore) - visible indicators for railgun rotation limits @@ -6099,7 +6151,7 @@ Multiplayer: - major changes to the networking code: better lag compensation, more reliable item/character syncing, lower bandwidth consumption - spectator mode - + Submarine: - overloading the electrical grid or the reactor may cause fires @@ -6120,7 +6172,7 @@ Items: Misc: - fixed placing ladders and labels in sub editor - fixed a couple of game-crashing bugs in submarine saving - + --------------------------------------------------------------------------------------------------------- v0.2.5 --------------------------------------------------------------------------------------------------------- @@ -6129,7 +6181,7 @@ Multiplayer: - option to randomly select level seed, submarine and/or game mode - players can be allowed to vote for the next sub and game mode - option to choose character's head - + Submarine: - pressure damage if the submarine dives too deep - added the missing mechanic spawnpoint missing to Aegir @@ -6141,7 +6193,7 @@ Items: - diving suits and mask now obstruct vision when worn - nicer looking sonar monitor -Misc: +Misc: - the levels aren't just enclosed tunnels anymore and it's possible to dive much deeper - settings menu - better UI scaling on small resolutions @@ -6164,7 +6216,7 @@ Multiplayer: - the "fix list" when repairing items is synced between clients, so the reactor can actually be fixed now - more networking optimization - bans can be removed by using a button under the player list, not just by editing the bannedplayers.xml file - + Items: - wires are removed from connection panels when they're deleted in the editor - doors can be rewired from either side @@ -6231,7 +6283,7 @@ v0.2.2 Multiplayer: - network statistics view which can be enabled by opening the debug console (F3) and entering "netstats" (only works if you're running a server) - - updated to latest version of Lidgren networking library, which may or may not have an effect + - updated to latest version of Lidgren networking library, which may or may not have an effect on the chat lag issues Items: @@ -6261,7 +6313,7 @@ Items: - broken doors can only be fixed by mechanics - fixed a bug that sometimes made it impossible to pick/select items after reattaching them on a wall - wires are disconnected and dropped if the item at either end is removed - + --------------------------------------------------------------------------------------------------------- v0.2 --------------------------------------------------------------------------------------------------------- @@ -6310,16 +6362,16 @@ Misc: added in future versions, including one for making custom subs) - an auto-updater in the launcher - the game generates a detailed report if it crashes - - physics optimization (i.e. using simplified physics and animation for off-screen characters and + - physics optimization (i.e. using simplified physics and animation for off-screen characters and disabling them entirely if they're far enough) - - lighting optimization (caching the lights/shadows if a light source hasn't moved instead of + - lighting optimization (caching the lights/shadows if a light source hasn't moved instead of recalculating them every frame) - two new background music tracks - better looking explosions - better looking water particle effects - minor UI improvements - better UI scaling on different resolutions - - health/oxygen bar improvements and status icons for bleeding and water pressure + - health/oxygen bar improvements and status icons for bleeding and water pressure - gap-hull connections are visible in the sub editor - pumps don't have to be manually connected to a hull in the editor anymore, they automatically empty/fill the hull they're inside @@ -6338,7 +6390,7 @@ Multiplayer: to a specific position - a window that displays some network statistics when hosting a server (can be activated by entering "debugview" to the debug console) - + --------------------------------------------------------------------------------------------------------- v0.1.3.1 --------------------------------------------------------------------------------------------------------- @@ -6356,12 +6408,12 @@ Multiplayer: to fly back to it as they try to move away Items: - - putting items inside other items works properly now (i.e. by pulling a spear to the same slot as + - putting items inside other items works properly now (i.e. by pulling a spear to the same slot as a harpoon, not the other way around) - C4 blocks loaded inside a railgun shell won't explode inside the submarine when firing the railgun - fixed another game-crashing railgun bug - fixed a bug that caused characters to spawn with an incorrect number of items - + --------------------------------------------------------------------------------------------------------- v0.1.2 --------------------------------------------------------------------------------------------------------- @@ -6376,7 +6428,7 @@ Items: Other: - optimized lightning and "line of sight" rendering - - an unfinished tutorial which can currently only be accessed by entering "tutorial" into the + - an unfinished tutorial which can currently only be accessed by entering "tutorial" into the debug console --------------------------------------------------------------------------------------------------------- @@ -6385,7 +6437,7 @@ v0.1.1 Multiplayer: - player names are shown - - assigning jobs and selecting job preferences works now (jobs are assigned when the round starts) + - assigning jobs and selecting job preferences works now (jobs are assigned when the round starts) - a menu that shows the crew members and their jobs and skills - reduced lag spikes - fixed a bug that caused disconnected players to stay in the player list diff --git a/Barotrauma/BarotraumaShared/config.xml b/Barotrauma/BarotraumaShared/config.xml index 62d4cca5c..4f6ecf964 100644 --- a/Barotrauma/BarotraumaShared/config.xml +++ b/Barotrauma/BarotraumaShared/config.xml @@ -31,7 +31,7 @@ Down="S" Left="A" Right="D" - Attack="R" + Attack="F" Run="LeftShift" Crouch="LeftControl" InfoTab="Tab" diff --git a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs index 53ce244d8..8b1ddfa8d 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs @@ -999,7 +999,7 @@ namespace FarseerPhysics.Dynamics if (body == null) throw new ArgumentNullException("body"); if (body.World != this) - throw new ArgumentException("You are removing a body that is not in the simulation.", "body"); + throw new ArgumentException($"You are removing a body that is not in the simulation (userdata: {body.UserData?.ToString() ?? "null"}).", "body"); #if USE_AWAKE_BODY_SET Debug.Assert(!AwakeBodySet.Contains(body));