From ecb6d40b4b2e6fd0e48b98448bdb798ba01a70b0 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Fri, 18 Nov 2022 18:13:38 +0200 Subject: [PATCH] Build 0.20.7.0 --- .../ClientSource/Characters/Character.cs | 7 +- .../Characters/Health/CharacterHealth.cs | 1 + .../ClientSource/DebugConsole.cs | 9 ++ .../Events/EventActions/UIHighlightAction.cs | 60 ++++++--- .../ClientSource/Fonts/ScalableFont.cs | 27 +++- .../ClientSource/GUI/GUIListBox.cs | 1 - .../ClientSource/GUI/GUINumberInput.cs | 27 ++-- .../ClientSource/GUI/GUIPrefab.cs | 82 ++++++++---- .../ClientSource/GUI/Store.cs | 6 +- .../ClientSource/GUI/TalentMenu.cs | 15 ++- .../BarotraumaClient/ClientSource/GameMain.cs | 3 +- .../Items/Components/ElectricalDischarger.cs | 31 +++-- .../Items/Components/Machines/Fabricator.cs | 93 ++++++++++++-- .../Items/Components/Machines/Sonar.cs | 19 ++- .../ClientSource/Items/Components/Turret.cs | 8 ++ .../ClientSource/Map/Lights/LightManager.cs | 25 ++-- .../ClientSource/Map/Map/Map.cs | 26 ++-- .../ClientSource/Networking/Client.cs | 70 +++++++++-- .../ClientSource/Networking/GameClient.cs | 2 +- .../Primitives/Peers/LidgrenClientPeer.cs | 2 +- .../Networking/Voip/VoipCapture.cs | 7 ++ .../Networking/Voip/VoipClient.cs | 41 +++--- .../MultiPlayerCampaignSetupUI.cs | 8 +- .../ClientSource/Sounds/OggSound.cs | 6 +- .../ClientSource/Sounds/Sound.cs | 6 +- .../ClientSource/Sounds/SoundChannel.cs | 39 +++--- .../ClientSource/Sounds/VoipSound.cs | 17 ++- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../Items/Components/Machines/Fabricator.cs | 7 +- .../Networking/Voip/VoipServer.cs | 70 +++++++---- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../SharedSource/Characters/AI/AITarget.cs | 12 +- .../Characters/AI/EnemyAIController.cs | 10 +- .../AI/Objectives/AIObjectiveRescue.cs | 22 +--- .../Animation/HumanoidAnimController.cs | 36 +++--- .../SharedSource/Characters/Character.cs | 65 ++++++++-- .../SharedSource/Characters/CharacterInfo.cs | 52 ++++---- .../Health/Afflictions/Affliction.cs | 12 +- .../Health/Afflictions/AfflictionHusk.cs | 78 ++++++------ .../Characters/Health/CharacterHealth.cs | 18 ++- .../Abilities/CharacterAbilityApplyForce.cs | 1 - ...racterAbilityApplyStatusEffectsToAllies.cs | 4 +- .../CharacterAbilityGainSimultaneousSkill.cs | 16 +-- .../CharacterAbilityGiveAffliction.cs | 6 +- .../Abilities/CharacterAbilityGiveFlag.cs | 4 +- .../Abilities/CharacterAbilityGiveMoney.cs | 9 +- .../CharacterAbilityGivePermanentStat.cs | 8 +- .../CharacterAbilityGiveReputation.cs | 8 ++ .../CharacterAbilityGiveResistance.cs | 14 ++- .../Abilities/CharacterAbilityGiveStat.cs | 4 +- .../CharacterAbilityGiveTalentPoints.cs | 11 +- ...haracterAbilityGiveTalentPointsToAllies.cs | 4 + .../CharacterAbilityIncreaseSkill.cs | 2 - .../Abilities/CharacterAbilityMarkAsLooted.cs | 4 + .../Abilities/CharacterAbilityModifyFlag.cs | 5 +- .../CharacterAbilityModifyResistance.cs | 26 ++-- .../Abilities/CharacterAbilityModifyStat.cs | 4 +- .../CharacterAbilityModifyStatToLevel.cs | 1 - .../CharacterAbilityModifyStatToSkill.cs | 1 - .../Abilities/CharacterAbilityModifyValue.cs | 8 +- .../Abilities/CharacterAbilityPutItem.cs | 4 +- .../CharacterAbilityResetPermanentStat.cs | 6 +- .../Abilities/CharacterAbilityRevive.cs | 5 +- .../CharacterAbilitySetMetadataInt.cs | 4 + .../CharacterAbilitySpawnItemsToContainer.cs | 1 - .../CharacterAbilityAlienHoarder.cs | 2 - .../CharacterAbilityApprenticeship.cs | 4 +- .../CharacterAbilityAtmosMachine.cs | 3 - .../CharacterAbilityBountyHunter.cs | 7 +- .../CharacterAbilityByTheBook.cs | 1 - .../CharacterAbilityMultitasker.cs | 5 +- .../CharacterAbilityPsychoClown.cs | 10 +- .../CharacterAbilityRegenerateLoot.cs | 3 - ...erAbilityUnlockApprenticeshipTalentTree.cs | 2 +- .../Characters/Talents/TalentTree.cs | 55 ++++++-- .../Events/EventActions/CheckOrderAction.cs | 2 +- .../Events/EventActions/UIHighlightAction.cs | 3 + .../SharedSource/Events/EventManager.cs | 68 ++++------ .../SharedSource/Events/EventSet.cs | 13 +- .../SharedSource/Events/Missions/Mission.cs | 22 ++-- .../GameSession/AutoItemPlacer.cs | 14 +-- .../Items/Components/ElectricalDischarger.cs | 54 +++++--- .../Items/Components/Holdable/Propulsion.cs | 3 +- .../Items/Components/ItemContainer.cs | 1 + .../Items/Components/Machines/Controller.cs | 1 + .../Items/Components/Machines/Fabricator.cs | 55 +++++--- .../Items/Components/Machines/Sonar.cs | 1 - .../SharedSource/Items/Components/Quality.cs | 33 +++-- .../SharedSource/Items/Item.cs | 8 +- .../SharedSource/Items/ItemStatManager.cs | 5 +- .../SharedSource/Map/Levels/Level.cs | 26 +--- .../SharedSource/Map/Levels/LevelData.cs | 37 ++++-- .../SharedSource/Map/Map/Location.cs | 21 +++- .../SharedSource/Map/Map/LocationType.cs | 4 +- .../Map/Outposts/OutpostGenerationParams.cs | 15 +++ .../SharedSource/Networking/ChatMessage.cs | 16 ++- .../Primitives/Address/LidgrenAddress.cs | 11 +- .../Primitives/Endpoint/LidgrenEndpoint.cs | 2 +- .../StatusEffects/DelayedEffect.cs | 6 +- .../StatusEffects/PropertyConditional.cs | 32 +++-- .../StatusEffects/StatusEffect.cs | 41 ++++-- .../SharedSource/Text/TextManager.cs | 118 +++++++++++++----- .../SharedSource/Utils/MathUtils.cs | 32 +++-- .../SharedSource/Utils/ToolBox.cs | 24 +++- Barotrauma/BarotraumaShared/changelog.txt | 44 +++++++ .../BarotraumaTest/GenericToolBoxTests.cs | 1 + ...tSerializableStructImplementationChecks.cs | 50 +++++++- 111 files changed, 1346 insertions(+), 701 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 7cbb4e91d..e2000f3f4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -657,12 +657,9 @@ namespace Barotrauma { if (Controlled == null || Controlled == this || (Controlled.CharacterHealth.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f) { - InvisibleTimer = 0.0f; - } - else - { - InvisibleTimer -= deltaTime; + InvisibleTimer = Math.Min(InvisibleTimer, 1.0f); } + InvisibleTimer -= deltaTime; } foreach (GUIMessage message in guiMessages) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 05903b126..a2dca3ea6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -394,6 +394,7 @@ namespace Barotrauma showHiddenAfflictionsButton = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: "GUIButtonCircular") { + Visible = false, CanBeFocused = false }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index f3350b2ed..46f811ccf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -2219,6 +2219,15 @@ namespace Barotrauma } })); + commands.Add(new Command("spawnallitems", "", (string[] args) => + { + var cursorPos = Screen.Selected.Cam?.ScreenToWorld(PlayerInput.MousePosition) ?? Vector2.Zero; + foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) + { + Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, cursorPos); + } + })); + commands.Add(new Command("camerasettings", "camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (string[] args) => { float defaultZoom = Screen.Selected.Cam.DefaultZoom; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/UIHighlightAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/UIHighlightAction.cs index 2826454ca..809bdb393 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/UIHighlightAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/UIHighlightAction.cs @@ -1,4 +1,5 @@ using Microsoft.Xna.Framework; +using System; using System.Linq; namespace Barotrauma; @@ -10,37 +11,66 @@ partial class UIHighlightAction : EventAction partial void UpdateProjSpecific() { bool useCircularFlash = false; - GUIComponent component = null; - if (Id != ElementId.None) { - component = GUI.GetAdditions().FirstOrDefault(c => Equals(Id, c.UserData)); + FindAndFlashComponents(c => Equals(Id, c.UserData)); } else if (!EntityIdentifier.IsEmpty) { - component = GUI.GetAdditions().FirstOrDefault(c => + FindAndFlashComponents(c => c.UserData is MapEntityPrefab mep && mep.Identifier == EntityIdentifier || c.UserData is MapEntity me && me.Prefab.Identifier == EntityIdentifier); } else if (!OrderIdentifier.IsEmpty) { useCircularFlash = true; + bool foundMinimapNode = false; if (!OrderTargetTag.IsEmpty) { - component = - GUI.GetAdditions().FirstOrDefault(c => - c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order && - order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag)); + foundMinimapNode = FindAndFlashComponents(c => + c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order && + order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag)); + } + if (!foundMinimapNode) + { + FindAndFlashComponents(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption, + c => c.UserData is Order order && order.Identifier == OrderIdentifier, + c => Equals(OrderCategory, c.UserData)); } - component ??= - GUI.GetAdditions().FirstOrDefault(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption) ?? - GUI.GetAdditions().FirstOrDefault(c => c.UserData is Order order && order.Identifier == OrderIdentifier) ?? - GUI.GetAdditions().FirstOrDefault(c => Equals(OrderCategory, c.UserData)); } - if (component != null && component.FlashTimer <= 0.0f) + bool FindAndFlashComponents(params Func[] predicates) { - component.Flash(highlightColor, useCircularFlash: useCircularFlash); - component.Bounce |= Bounce; + foreach (var predicate in predicates) + { + if (HighlightMultiple) + { + bool found = false; + foreach (var component in GUI.GetAdditions()) + { + if (predicate(component)) + { + Flash(component); + found = true; + } + }; + return found; + } + else if (GUI.GetAdditions().FirstOrDefault(predicate) is GUIComponent component) + { + Flash(component); + return true; + } + } + return false; + } + + void Flash(GUIComponent component) + { + if (component.FlashTimer <= 0.0f) + { + component.Flash(highlightColor, useCircularFlash: useCircularFlash); + component.Bounce |= Bounce; + } } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs b/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs index b7b05c7f0..1aa23640c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; +using System.Xml.Linq; using Barotrauma.Threading; namespace Barotrauma @@ -37,7 +38,7 @@ namespace Barotrauma private set; } - public bool IsCJK + public TextManager.SpeciallyHandledCharCategory SpeciallyHandledCharCategory { get; private set; @@ -84,17 +85,35 @@ namespace Barotrauma } } + public static TextManager.SpeciallyHandledCharCategory ExtractShccFromXElement(XElement element) + => TextManager.SpeciallyHandledCharCategories + .Where(category => element.GetAttributeBool($"is{category}", category switch { + // CJK isn't supported by default + TextManager.SpeciallyHandledCharCategory.CJK => false, + + // For backwards compatibility, we assume that Cyrillic is supported by default + TextManager.SpeciallyHandledCharCategory.Cyrillic => true, + + _ => throw new Exception("unreachable") + })) + .Aggregate(TextManager.SpeciallyHandledCharCategory.None, (current, category) => current | category); + public ScalableFont(ContentXElement element, GraphicsDevice gd = null) : this( element.GetAttributeContentPath("file")?.Value, (uint)element.GetAttributeInt("size", 14), gd, element.GetAttributeBool("dynamicloading", false), - element.GetAttributeBool("iscjk", false)) + ExtractShccFromXElement(element)) { } - public ScalableFont(string filename, uint size, GraphicsDevice gd = null, bool dynamicLoading = false, bool isCJK = false) + public ScalableFont( + string filename, + uint size, + GraphicsDevice gd = null, + bool dynamicLoading = false, + TextManager.SpeciallyHandledCharCategory speciallyHandledCharCategory = TextManager.SpeciallyHandledCharCategory.None) { lock (globalMutex) { @@ -120,7 +139,7 @@ namespace Barotrauma this.textures = new List(); this.texCoords = new Dictionary(); this.DynamicLoading = dynamicLoading; - this.IsCJK = isCJK; + this.SpeciallyHandledCharCategory = speciallyHandledCharCategory; this.graphicsDevice = gd; if (gd != null && !dynamicLoading) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index fc0b5c098..d5f2e3a7e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -150,7 +150,6 @@ namespace Barotrauma } } - // TODO: fix implicit hiding public override bool Selected { get { return isSelected; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs index f3802cca6..4f938a89f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs @@ -179,6 +179,11 @@ namespace Barotrauma private set; } + /// + /// If enabled, the value wraps around to Max when you go below Min, and vice versa + /// + public bool WrapAround; + public float valueStep; private float pressedTimer; @@ -403,13 +408,19 @@ namespace Barotrauma { if (MinValueFloat != null) { - floatValue = Math.Max(floatValue, MinValueFloat.Value); - MinusButton.Enabled = floatValue > MinValueFloat; + floatValue = + WrapAround && MinValueFloat.HasValue && floatValue < MinValueFloat.Value ? + MaxValueFloat.Value : + Math.Max(floatValue, MinValueFloat.Value); + MinusButton.Enabled = WrapAround || floatValue > MinValueFloat; } if (MaxValueFloat != null) { - floatValue = Math.Min(floatValue, MaxValueFloat.Value); - PlusButton.Enabled = floatValue < MaxValueFloat; + floatValue = + WrapAround && MaxValueFloat.HasValue && floatValue > MaxValueFloat.Value ? + MinValueFloat.Value : + Math.Min(floatValue, MaxValueFloat.Value); + PlusButton.Enabled = WrapAround || floatValue < MaxValueFloat; } } @@ -417,16 +428,16 @@ namespace Barotrauma { if (MinValueInt != null && intValue < MinValueInt.Value) { - intValue = Math.Max(intValue, MinValueInt.Value); + intValue = WrapAround && MaxValueInt.HasValue ? MaxValueInt.Value : Math.Max(intValue, MinValueInt.Value); UpdateText(); } if (MaxValueInt != null && intValue > MaxValueInt.Value) { - intValue = Math.Min(intValue, MaxValueInt.Value); + intValue = WrapAround && MinValueInt.HasValue ? MinValueInt.Value : Math.Min(intValue, MaxValueInt.Value); UpdateText(); } - PlusButton.Enabled = MaxValueInt == null || intValue < MaxValueInt; - MinusButton.Enabled = MinValueInt == null || intValue > MinValueInt; + PlusButton.Enabled = WrapAround || MaxValueInt == null || intValue < MaxValueInt; + MinusButton.Enabled = WrapAround || MinValueInt == null || intValue > MinValueInt; } private void UpdateText() diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIPrefab.cs index 8ae5366b8..d3ab8134d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIPrefab.cs @@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Xml.Linq; +using Barotrauma.Extensions; namespace Barotrauma { @@ -45,16 +46,17 @@ namespace Barotrauma } } - private ScalableFont cjkFont; + private ImmutableDictionary specialHandlingFonts; - public ScalableFont CjkFont + public ScalableFont GetFontForCategory(TextManager.SpeciallyHandledCharCategory category) { - get - { - if (Language != GameSettings.CurrentConfig.Language) { LoadFont(); } - if (font.IsCJK) { return font; } - return cjkFont; - } + if (Language != GameSettings.CurrentConfig.Language) { LoadFont(); } + if (font.SpeciallyHandledCharCategory.HasFlag(category)) { return font; } + return specialHandlingFonts.TryGetValue(category, out var resultFont) + ? resultFont + : specialHandlingFonts.TryGetValue(TextManager.SpeciallyHandledCharCategory.CJK, out resultFont) + ? resultFont + : font; } public LanguageIdentifier Language { get; private set; } @@ -70,40 +72,68 @@ namespace Barotrauma string fontPath = GetFontFilePath(element); uint size = GetFontSize(element); bool dynamicLoading = GetFontDynamicLoading(element); - bool isCJK = GetIsCJK(element); + var shcc = GetShcc(element); font?.Dispose(); - cjkFont?.Dispose(); - font = new ScalableFont(fontPath, size, GameMain.Instance.GraphicsDevice, dynamicLoading, isCJK) + specialHandlingFonts?.Values.ForEach(f => f.Dispose()); + font = new ScalableFont( + fontPath, + size, + GameMain.Instance.GraphicsDevice, + dynamicLoading, + shcc) { ForceUpperCase = element.GetAttributeBool("forceuppercase", false) }; - if (!isCJK) + + var fallbackFonts = new Dictionary(); + foreach (var flag in TextManager.SpeciallyHandledCharCategories) { - cjkFont = ExtractCjkFont(element) - ?? new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf", - font.Size, GameMain.Instance.GraphicsDevice, dynamicLoading: true, isCJK: true); - cjkFont.ForceUpperCase = font.ForceUpperCase; + if (shcc.HasFlag(flag)) { continue; } + + var extractedFont = ExtractFont(flag, element); + if (extractedFont is null) { continue; } + fallbackFonts.Add(flag, extractedFont); } + fallbackFonts.Values.ForEach(ff => ff.ForceUpperCase = font.ForceUpperCase); + specialHandlingFonts = fallbackFonts.ToImmutableDictionary(); Language = GameSettings.CurrentConfig.Language; } public override void Dispose() { - font?.Dispose(); font = null; - cjkFont?.Dispose(); cjkFont = null; + font?.Dispose(); + font = null; + specialHandlingFonts?.Values.ForEach(f => f.Dispose()); + specialHandlingFonts = null; } - private ScalableFont ExtractCjkFont(ContentXElement element) + private ScalableFont ExtractFont(TextManager.SpeciallyHandledCharCategory flag, ContentXElement element) { foreach (var subElement in element.Elements().Reverse()) { if (subElement.NameAsIdentifier() != "override") { continue; } - if (subElement.GetAttributeBool("iscjk", false)) + if (ScalableFont.ExtractShccFromXElement(subElement).HasFlag(flag)) { return new ScalableFont(subElement, GameMain.Instance.GraphicsDevice); } } - return null; + + ScalableFont hardcodedFallback(string path) + => new ScalableFont( + path, + font.Size, + GameMain.Instance.GraphicsDevice, + dynamicLoading: true, + speciallyHandledCharCategory: flag); + + return flag switch + { + TextManager.SpeciallyHandledCharCategory.CJK + => hardcodedFallback("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf"), + TextManager.SpeciallyHandledCharCategory.Cyrillic + => hardcodedFallback("Content/Fonts/Oswald-Bold.ttf"), + _ => null + }; } private string GetFontFilePath(ContentXElement element) @@ -154,21 +184,21 @@ namespace Barotrauma return element.GetAttributeBool("dynamicloading", false); } - private bool GetIsCJK(XElement element) + private TextManager.SpeciallyHandledCharCategory GetShcc(XElement element) { foreach (var subElement in element.Elements()) { if (IsValidOverride(subElement)) { - return subElement.GetAttributeBool("iscjk", false); + return ScalableFont.ExtractShccFromXElement(subElement); } } - return element.GetAttributeBool("iscjk", false); + return ScalableFont.ExtractShccFromXElement(element); } private bool IsValidOverride(XElement element) { - if (!element.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { return false; } + if (!element.IsOverride()) { return false; } var languages = element.GetAttributeIdentifierArray("language", Array.Empty()); return languages.Any(l => l.ToLanguageIdentifier() == GameSettings.CurrentConfig.Language); } @@ -191,7 +221,7 @@ namespace Barotrauma private ScalableFont GetFontForStr(LocalizedString str) => GetFontForStr(str.Value); public ScalableFont GetFontForStr(string str) => - TextManager.IsCJK(str) ? Prefabs.ActivePrefab.CjkFont : Prefabs.ActivePrefab.Font; + Prefabs.ActivePrefab.GetFontForCategory(TextManager.GetSpeciallyHandledCategories(str)); public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index a9a532278..b166333db 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -617,9 +617,9 @@ namespace Barotrauma Stretch = true }; var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.8f), shoppingCrateInventoryContainer.RectTransform), style: null); - shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; - shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; - shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; + shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false, KeepSpaceForScrollBar = true }; + shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false, KeepSpaceForScrollBar = true }; + shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false, KeepSpaceForScrollBar = true }; var relevantBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs index cdb5059d9..477598633 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs @@ -325,7 +325,7 @@ namespace Barotrauma } var specializationList = GetSpecializationList(); - GetSpecializationList().RectTransform.Resize(new Point(specializationList.Content.Children.Sum(static c => c.Rect.Width), specializationList.Rect.Height)); + GetSpecializationList().Content.RectTransform.Resize(new Point(specializationList.Content.Children.Sum(static c => c.Rect.Width), specializationList.Rect.Height), resizeChildren: false); GUITextBlock.AutoScaleAndNormalize(subTreeNames); @@ -439,7 +439,8 @@ namespace Barotrauma } if (character is null) { return false; } - + + Identifier talentIdentifier = (Identifier)userData; if (talentOption.MaxChosenTalents is 1) { // deselect other buttons in tier by removing their selected talents from pool @@ -454,9 +455,11 @@ namespace Barotrauma } } - Identifier talentIdentifier = (Identifier)userData; - - if (IsViableTalentForCharacter(info.Character, talentIdentifier, selectedTalents)) + if (character.HasTalent(talentIdentifier)) + { + return true; + } + else if (IsViableTalentForCharacter(info.Character, talentIdentifier, selectedTalents)) { if (!selectedTalents.Contains(talentIdentifier)) { @@ -467,7 +470,7 @@ namespace Barotrauma selectedTalents.Remove(talentIdentifier); } } - else if (!character.HasTalent(talentIdentifier)) + else { selectedTalents.Remove(talentIdentifier); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 21dc20279..d5a7f5cb4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -314,7 +314,7 @@ namespace Barotrauma GraphicsDeviceManager.SynchronizeWithVerticalRetrace = GameSettings.CurrentConfig.Graphics.VSync; SetWindowMode(GameSettings.CurrentConfig.Graphics.DisplayMode); - defaultViewport = GraphicsDevice.Viewport; + defaultViewport = new Viewport(0, 0, GraphicsWidth, GraphicsHeight); if (recalculateFontsAndStyles) { @@ -353,6 +353,7 @@ namespace Barotrauma public void ResetViewPort() { GraphicsDevice.Viewport = defaultViewport; + GraphicsDevice.ScissorRectangle = defaultViewport.Bounds; } /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ElectricalDischarger.cs index f649e423c..fd10eb0f0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ElectricalDischarger.cs @@ -28,18 +28,27 @@ namespace Barotrauma.Items.Components if (node.ParentIndex > -1) { - Vector2 diff = nodes[node.ParentIndex].WorldPosition - node.WorldPosition; - float dist = diff.Length(); - Vector2 normalizedDiff = diff / dist; - for (float x = 0.0f; x < dist; x += 50.0f) - { - var spark = GameMain.ParticleManager.CreateParticle("ElectricShock", node.WorldPosition + normalizedDiff * x, Vector2.Zero); - if (spark != null) - { - spark.Size *= 0.3f; - } - } + CreateParticlesBetween(nodes[node.ParentIndex].WorldPosition, node.WorldPosition); + } + } + foreach (var character in charactersInRange) + { + CreateParticlesBetween(character.character.WorldPosition, character.node.WorldPosition); + } + static void CreateParticlesBetween(Vector2 start, Vector2 end) + { + const float ParticleInterval = 50.0f; + Vector2 diff = end - start; + float dist = diff.Length(); + Vector2 normalizedDiff = MathUtils.NearlyEqual(dist, 0.0f) ? Vector2.Zero : diff / dist; + for (float x = 0.0f; x < dist; x += ParticleInterval) + { + var spark = GameMain.ParticleManager.CreateParticle("ElectricShock", start + normalizedDiff * x, Vector2.Zero); + if (spark != null) + { + spark.Size *= 0.3f; + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index 16e766617..473e52099 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -14,7 +14,10 @@ namespace Barotrauma.Items.Components private GUIFrame selectedItemFrame; private GUIFrame selectedItemReqsFrame; - + + private GUITextBlock amountTextMin, amountTextMax; + private GUIScrollBar amountInput; + public GUIButton ActivateButton { get { return activateButton; } @@ -160,14 +163,46 @@ namespace Barotrauma.Items.Components new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawInputOverLay) { CanBeFocused = false }; // === ACTIVATE BUTTON === // - var buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterRight); - activateButton = new GUIButton(new RectTransform(new Vector2(1f, 0.6f), buttonFrame.RectTransform), - TextManager.Get(CreateButtonText), style: "DeviceButtonFixedSize") + var buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.9f), inputArea.RectTransform)) + { + Stretch = true, + RelativeSpacing = 0.05f + }; + + var amountInputHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.4f), buttonFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + Stretch = true + }; + + amountTextMin = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center); + + amountInput = new GUIScrollBar(new RectTransform(new Vector2(0.7f, 1.0f), amountInputHolder.RectTransform), barSize: 0.1f, style: "GUISlider") + { + OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + scrollBar.Step = 1.0f / Math.Max(scrollBar.Range.Y - 1, 1); + AmountToFabricate = (int)MathF.Round(scrollBar.BarScrollValue); + RefreshActivateButtonText(); + if (GameMain.Client != null) + { + item.CreateClientEvent(this); + } + return true; + } + }; + + amountTextMax = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center); + + activateButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.6f), buttonFrame.RectTransform), + TextManager.Get(CreateButtonText), style: "DeviceButton") { OnClicked = StartButtonClicked, UserData = selectedItem, Enabled = false - }; + }; + + //spacing + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), buttonFrame.RectTransform), style: null); } else { @@ -192,6 +227,21 @@ namespace Barotrauma.Items.Components CreateRecipes(); } + private void RefreshActivateButtonText() + { + if (amountInput == null) + { + activateButton.Text = TextManager.Get(IsActive ? "FabricatorCancel" : CreateButtonText); + } + else + { + activateButton.Text = + IsActive ? + $"{TextManager.Get("FabricatorCancel")} ({amountRemaining})" : + $"{TextManager.Get(CreateButtonText)} ({AmountToFabricate})"; + } + } + partial void CreateRecipes() { itemList.Content.RectTransform.ClearChildren(); @@ -247,7 +297,7 @@ namespace Barotrauma.Items.Components outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform; } - private LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe) + private static LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe) { if (fabricationRecipe == null) { return ""; } if (fabricationRecipe.Amount > 1) @@ -269,7 +319,7 @@ namespace Barotrauma.Items.Components partial void SelectProjSpecific(Character character) { - var nonItems = itemList.Content.Children.Where(c => !(c.UserData is FabricationRecipe)).ToList(); + var nonItems = itemList.Content.Children.Where(c => c.UserData is not FabricationRecipe).ToList(); nonItems.ForEach(i => itemList.Content.RemoveChild(i)); itemList.Content.RectTransform.SortChildren((c1, c2) => @@ -567,7 +617,7 @@ namespace Barotrauma.Items.Components bool recipeVisible = false; foreach (GUIComponent child in itemList.Content.Children.Reverse()) { - if (!(child.UserData is FabricationRecipe recipe)) + if (child.UserData is not FabricationRecipe recipe) { if (child.Enabled) { @@ -598,9 +648,23 @@ namespace Barotrauma.Items.Components { this.selectedItem = selectedItem; + int max = Math.Max(selectedItem.TargetItem.MaxStackSize / selectedItem.Amount, 1); + + if (amountInput != null) + { + float prevBarScroll = amountInput.BarScroll; + amountInput.Range = new Vector2(1, max); + amountInput.BarScroll = prevBarScroll; + + amountTextMax.Text = max.ToString(); + amountInput.Enabled = amountTextMax.Enabled = max > 1; + AmountToFabricate = Math.Min((int)amountInput.BarScrollValue, max); + } + RefreshActivateButtonText(); + selectedItemFrame.ClearChildren(); selectedItemReqsFrame.ClearChildren(); - + var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f }; var paddedReqFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f }; @@ -734,7 +798,9 @@ namespace Barotrauma.Items.Components outputSlot.Flash(GUIStyle.Red); return false; } - + + amountRemaining = AmountToFabricate; + if (GameMain.Client != null) { pendingFabricatedItem = fabricatedItem != null ? null : selectedItem; @@ -777,7 +843,7 @@ namespace Barotrauma.Items.Components { foreach (GUIComponent child in itemList.Content.Children) { - if (!(child.UserData is FabricationRecipe recipe)) { continue; } + if (child.UserData is not FabricationRecipe recipe) { continue; } if (recipe != selectedItem && (child.Rect.Y > itemList.Rect.Bottom || child.Rect.Bottom < itemList.Rect.Y)) @@ -811,11 +877,14 @@ namespace Barotrauma.Items.Components { uint recipeHash = pendingFabricatedItem?.RecipeHash ?? 0; msg.WriteUInt32(recipeHash); + msg.WriteRangedInteger(AmountToFabricate, 1, MaxAmountToFabricate); } public void ClientEventRead(IReadMessage msg, float sendingTime) { FabricatorState newState = (FabricatorState)msg.ReadByte(); + int amountToFabricate = msg.ReadRangedInteger(0, MaxAmountToFabricate); + int amountRemaining = msg.ReadRangedInteger(0, MaxAmountToFabricate); float newTimeUntilReady = msg.ReadSingle(); uint recipeHash = msg.ReadUInt32(); UInt16 userID = msg.ReadUInt16(); @@ -828,6 +897,8 @@ namespace Barotrauma.Items.Components } State = newState; + this.amountToFabricate = amountToFabricate; + this.amountRemaining = amountRemaining; if (newState == FabricatorState.Stopped || recipeHash == 0) { CancelFabricating(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index 90ff7ba05..7271fcfcb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -802,21 +802,34 @@ namespace Barotrauma.Items.Components if (passivePingRadius > 0.0f) { if (activePingsCount == 0) { disruptedDirections.Clear(); } + //emit "pings" from nearby sound-emitting AITargets to reveal what's around them foreach (AITarget t in AITarget.List) { if (t.Entity is Character c && !c.IsUnconscious && c.Params.HideInSonar) { continue; } if (t.SoundRange <= 0.0f || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; } - + float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter); if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; } float dist = (float)Math.Sqrt(distSqr); - if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500 && t.IsWithinSector(transducerCenter)) + if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500) { + int prevBlipCount = sonarBlips.Count; Ping(t.WorldPosition, transducerCenter, - Math.Min(t.SoundRange, range * 0.5f) * displayScale, 0, displayScale, Math.Min(t.SoundRange, range * 0.5f), + Math.Min(t.SoundRange, range * 0.5f) * displayScale, 0, displayScale, Math.Min(t.SoundRange, range * 0.5f), passive: true, pingStrength: 0.5f); sonarBlips.Add(new SonarBlip(t.WorldPosition, 1.0f, 1.0f)); + //remove blips that weren't in the AITarget's sector + if (t.HasSector()) + { + for (int i = sonarBlips.Count - 1; i >= prevBlipCount; i--) + { + if (!t.IsWithinSector(sonarBlips[i].Position)) + { + sonarBlips.RemoveAt(i); + } + } + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs index 701a5474e..e66a15de4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs @@ -411,6 +411,14 @@ namespace Barotrauma.Items.Components SpriteEffects.None, newDepth); } + if (GameMain.DebugDraw) + { + Vector2 firingPos = GetRelativeFiringPosition(); + firingPos.Y = -firingPos.Y; + GUI.DrawLine(spriteBatch, firingPos - Vector2.UnitX * 5, firingPos + Vector2.UnitX * 5, Color.Red); + GUI.DrawLine(spriteBatch, firingPos - Vector2.UnitY * 5, firingPos + Vector2.UnitY * 5, Color.Red); + } + if (!editing || GUI.DisableHUD || !item.IsSelected) { return; } const float widgetRadius = 60.0f; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs index ca5456156..e3aec5d91 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs @@ -343,38 +343,31 @@ namespace Barotrauma.Lights { SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidVertexColor"]; spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform); - foreach (Character character in Character.CharacterList) - { - if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; } - if (Character.Controlled?.FocusedCharacter == character) { continue; } - Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? - Color.Black : - character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque(); - foreach (Limb limb in character.AnimController.Limbs) - { - if (limb.DeformSprite != null) { continue; } - limb.Draw(spriteBatch, cam, lightColor); - } - } + DrawCharacters(spriteBatch, cam, drawDeformSprites: false); spriteBatch.End(); DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidVertexColor"]; DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply(); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform); + DrawCharacters(spriteBatch, cam, drawDeformSprites: true); + spriteBatch.End(); + } + + static void DrawCharacters(SpriteBatch spriteBatch, Camera cam, bool drawDeformSprites) + { foreach (Character character in Character.CharacterList) { - if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; } + if (character.CurrentHull == null || !character.Enabled || !character.IsVisible || character.InvisibleTimer > 0.0f) { continue; } if (Character.Controlled?.FocusedCharacter == character) { continue; } Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? Color.Black : character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque(); foreach (Limb limb in character.AnimController.Limbs) { - if (limb.DeformSprite == null) { continue; } + if (drawDeformSprites == (limb.DeformSprite == null)) { continue; } limb.Draw(spriteBatch, cam, lightColor); } } - spriteBatch.End(); } DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"]; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs index db0dcb504..8339eb987 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs @@ -697,17 +697,22 @@ namespace Barotrauma Vector2 size = new Vector2(Math.Max(nameSize.X, Math.Max(typeSize.X, descSize.X)), nameSize.Y + typeSize.Y + descSize.Y); int highestSubTier = HighlightedLocation.HighestSubmarineTierAvailable(); - var overrideTiers = new List<(SubmarineClass subClass, int tier)>(); - foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass))) + List<(SubmarineClass subClass, int tier)> overrideTiers = null; + if (HighlightedLocation.CanHaveSubsForSale()) { - if (subClass == SubmarineClass.Undefined) { continue; } - int highestClassTier = HighlightedLocation.HighestSubmarineTierAvailable(subClass); - if (highestClassTier > 0 && highestClassTier > highestSubTier) + overrideTiers = new List<(SubmarineClass subClass, int tier)>(); + foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass))) { - overrideTiers.Add((subClass, highestClassTier)); + if (subClass == SubmarineClass.Undefined) { continue; } + int highestClassTier = HighlightedLocation.HighestSubmarineTierAvailable(subClass); + if (highestClassTier > 0 && highestClassTier > highestSubTier) + { + overrideTiers.Add((subClass, highestClassTier)); + } } } - size.Y += ((highestSubTier > 0 ? 1 : 0) + overrideTiers.Count) * GUIStyle.SmallFont.MeasureString(TextManager.Get("advancedsub.all")).Y; + int subAvailabilityTextCount = (highestSubTier > 0 ? 1 : 0) + (overrideTiers?.Count ?? 0); + size.Y += subAvailabilityTextCount * GUIStyle.SmallFont.MeasureString(TextManager.Get("advancedsub.all")).Y; bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null; LocalizedString repLabelText = null, repValueText = null; @@ -745,9 +750,12 @@ namespace Barotrauma { DrawSubAvailabilityText("advancedsub.all", highestSubTier); } - foreach (var (subClass, tier) in overrideTiers) + if (overrideTiers != null) { - DrawSubAvailabilityText($"advancedsub.{subClass}", tier); + foreach (var (subClass, tier) in overrideTiers) + { + DrawSubAvailabilityText($"advancedsub.{subClass}", tier); + } } void DrawSubAvailabilityText(string tag, int tier) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs index 21b119c40..460d06df1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs @@ -14,6 +14,16 @@ namespace Barotrauma.Networking set; } + private SoundChannel radioNoiseChannel; + private float radioNoise; + + public float RadioNoise + { + get { return radioNoise; } + set { radioNoise = MathHelper.Clamp(value, 0.0f, 1.0f); } + } + + private bool mutedLocally; public bool MutedLocally { @@ -42,35 +52,64 @@ namespace Barotrauma.Networking !HasPermission(ClientPermissions.Kick) && !HasPermission(ClientPermissions.Unban); - public void UpdateSoundPosition() + public void UpdateVoipSound() { - if (VoipSound == null) { return; } - - if (!VoipSound.IsPlaying) + if (VoipSound == null || !VoipSound.IsPlaying) { - DebugConsole.Log("Destroying voipsound"); - VoipSound.Dispose(); + radioNoiseChannel?.Dispose(); + radioNoiseChannel = null; + if (VoipSound != null) + { + DebugConsole.Log("Destroying voipsound"); + VoipSound.Dispose(); + } VoipSound = null; - return; + return; } + if (Screen.Selected is ModDownloadScreen) + { + VoipSound.Gain = 0.0f; + } + + float gain = 1.0f; + float noiseGain = 0.0f; + Vector3? position = null; if (character != null) { if (GameSettings.CurrentConfig.Audio.UseDirectionalVoiceChat) { - VoipSound.SetPosition(new Vector3(character.WorldPosition.X, character.WorldPosition.Y, 0.0f)); + position = new Vector3(character.WorldPosition.X, character.WorldPosition.Y, 0.0f); } else { - VoipSound.SetPosition(null); float dist = Vector3.Distance(new Vector3(character.WorldPosition, 0.0f), GameMain.SoundManager.ListenerPosition); - VoipSound.Gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist); + gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist); + } + if (RadioNoise > 0.0f) + { + noiseGain = gain * RadioNoise; + gain *= 1.0f - RadioNoise; } } - else + VoipSound.SetPosition(position); + VoipSound.Gain = gain; + if (noiseGain > 0.0f) { - VoipSound.SetPosition(null); - VoipSound.Gain = 1.0f; + if (radioNoiseChannel == null || !radioNoiseChannel.IsPlaying) + { + radioNoiseChannel = SoundPlayer.PlaySound("radiostatic"); + radioNoiseChannel.Category = "voip"; + radioNoiseChannel.Looping = true; + } + radioNoiseChannel.Near = VoipSound.Near; + radioNoiseChannel.Far = VoipSound.Far; + radioNoiseChannel.Position = position; + radioNoiseChannel.Gain = noiseGain; + } + else if (radioNoiseChannel != null) + { + radioNoiseChannel.Gain = 0.0f; } } @@ -158,6 +197,11 @@ namespace Barotrauma.Networking VoipSound.Dispose(); VoipSound = null; } + if (radioNoiseChannel != null) + { + radioNoiseChannel.Dispose(); + radioNoiseChannel = null; + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 061191f03..a11aeb023 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -449,7 +449,7 @@ namespace Barotrauma.Networking foreach (Client c in ConnectedClients) { if (c.Character != null && c.Character.Removed) { c.Character = null; } - c.UpdateSoundPosition(); + c.UpdateVoipSound(); } if (VoipCapture.Instance != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs index a95afd553..262f608bc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs @@ -111,7 +111,7 @@ namespace Barotrauma.Networking foreach (NetIncomingMessage inc in incomingLidgrenMessages) { - if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint)) + if (!inc.SenderConnection.RemoteEndPoint.EquivalentTo(lidgrenEndpoint.NetEndpoint)) { DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}"); continue; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs index bfbba0c7f..5cf536458 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs @@ -260,6 +260,13 @@ namespace Barotrauma.Networking } } } + + if (Screen.Selected is ModDownloadScreen) + { + allowEnqueue = false; + captureTimer = 0; + } + if (allowEnqueue || captureTimer > 0) { LastEnqueueAudio = DateTime.Now; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs index 934820e0d..7698c4b66 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs @@ -1,20 +1,23 @@ -using Barotrauma.Sounds; +using Barotrauma.Items.Components; +using Barotrauma.Sounds; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Xna.Framework; -using Barotrauma.Items.Components; namespace Barotrauma.Networking { class VoipClient : IDisposable { - private GameClient gameClient; - private ClientPeer netClient; + /// + /// The "near" range of the voice chat (a percentage of either SpeakRange or radio range), further than this the volume starts to diminish + /// + const float RangeNear = 0.4f; + + private readonly GameClient gameClient; + private readonly ClientPeer netClient; private DateTime lastSendTime; - private List queues; + private readonly List queues; private UInt16 storedBufferID = 0; @@ -32,13 +35,13 @@ namespace Barotrauma.Networking public void RegisterQueue(VoipQueue queue) { - if (queue == VoipCapture.Instance) return; - if (!queues.Contains(queue)) queues.Add(queue); + if (queue == VoipCapture.Instance) { return; } + if (!queues.Contains(queue)) { queues.Add(queue); } } public void UnregisterQueue(VoipQueue queue) { - if (queues.Contains(queue)) queues.Remove(queue); + if (queues.Contains(queue)) { queues.Remove(queue); } } public void SendToServer() @@ -85,6 +88,7 @@ namespace Barotrauma.Networking public void Read(IReadMessage msg) { byte queueId = msg.ReadByte(); + float distanceFactor = msg.ReadRangedSingle(0.0f, 1.0f, 8); VoipQueue queue = queues.Find(q => q.QueueID == queueId); if (queue == null) @@ -105,9 +109,12 @@ namespace Barotrauma.Networking client.VoipSound = new VoipSound(client.Name, GameMain.SoundManager, client.VoipQueue); } GameMain.SoundManager.ForceStreamUpdate(); - + client.RadioNoise = 0.0f; if (client.Character != null && !client.Character.IsDead && !client.Character.Removed && client.Character.SpeechImpediment <= 100.0f) { + float speechImpedimentMultiplier = 1.0f - client.Character.SpeechImpediment / 100.0f; + bool spectating = Character.Controlled == null; + float rangeMultiplier = spectating ? 2.0f : 1.0f; WifiComponent radio = null; var messageType = !client.VoipQueue.ForceLocal && ChatMessage.CanUseRadio(client.Character, out radio) ? ChatMessageType.Radio : ChatMessageType.Default; client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]); @@ -115,11 +122,17 @@ namespace Barotrauma.Networking client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters; if (messageType == ChatMessageType.Radio) { - client.VoipSound.SetRange(radio.Range * 0.8f, radio.Range); + client.VoipSound.SetRange(radio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, radio.Range * speechImpedimentMultiplier * rangeMultiplier); + if (distanceFactor > RangeNear && !spectating) + { + //noise starts increasing exponentially after 40% range + client.RadioNoise = MathF.Pow(MathUtils.InverseLerp(RangeNear, 1.0f, distanceFactor), 2); + } } else { - client.VoipSound.SetRange(ChatMessage.SpeakRange * 0.4f, ChatMessage.SpeakRange); + + client.VoipSound.SetRange(ChatMessage.SpeakRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, ChatMessage.SpeakRange * speechImpedimentMultiplier * rangeMultiplier); } client.VoipSound.UseMuffleFilter = messageType != ChatMessageType.Radio && Character.Controlled != null && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters && diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs index 66f0877d6..1a19fc973 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs @@ -35,14 +35,14 @@ namespace Barotrauma }; // New game - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft); - saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, string.Empty) + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, GUI.IntScale(24)) }, TextManager.Get("SaveName"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft); + saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform), string.Empty) { textFilterFunction = ToolBox.RemoveInvalidFileNameChars }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft); - seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, GUI.IntScale(24)) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft); + seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform), ToolBox.RandomSeed(8)); nameSeedLayout.RectTransform.MinSize = new Point(0, nameSeedLayout.Children.Sum(c => c.RectTransform.MinSize.Y)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs index 20f85f5e4..1f1dca9e1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs @@ -11,7 +11,7 @@ namespace Barotrauma.Sounds private VorbisReader reader; //key = sample rate, value = filter - private static Dictionary muffleFilters = new Dictionary(); + private static readonly Dictionary muffleFilters = new Dictionary(); private static List playbackAmplitude; private const int AMPLITUDE_SAMPLE_COUNT = 4410; //100ms in a 44100hz file @@ -29,8 +29,8 @@ namespace Barotrauma.Sounds public override int FillStreamBuffer(int samplePos, short[] buffer) { - if (!Stream) throw new Exception("Called FillStreamBuffer on a non-streamed sound!"); - if (reader == null) throw new Exception("Called FillStreamBuffer when the reader is null!"); + if (!Stream) { throw new Exception("Called FillStreamBuffer on a non-streamed sound!"); } + if (reader == null) { throw new Exception("Called FillStreamBuffer when the reader is null!"); } if (samplePos >= reader.TotalSamples * reader.Channels * 2) return 0; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs index 3f4918c57..d911d4ac2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs @@ -24,11 +24,12 @@ namespace Barotrauma.Sounds public readonly bool StreamsReliably; + private readonly SoundManager.SourcePoolIndex sourcePoolIndex = SoundManager.SourcePoolIndex.Default; public virtual SoundManager.SourcePoolIndex SourcePoolIndex { get { - return SoundManager.SourcePoolIndex.Default; + return sourcePoolIndex; } } @@ -59,13 +60,14 @@ namespace Barotrauma.Sounds public float BaseNear; public float BaseFar; - public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, XElement xElement=null, bool getFullPath=true) + public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, XElement xElement = null, bool getFullPath = true) { Owner = owner; Filename = getFullPath ? Path.GetFullPath(filename.CleanUpPath()).CleanUpPath() : filename; Stream = stream; StreamsReliably = streamsReliably; XElement = xElement; + sourcePoolIndex = XElement.GetAttributeEnum("sourcepool", SoundManager.SourcePoolIndex.Default); BaseGain = 1.0f; BaseNear = 100.0f; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs index bacb39916..6f5b2efac 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs @@ -1,9 +1,7 @@ -using System; +using Microsoft.Xna.Framework; using OpenAL; -using Microsoft.Xna.Framework; -using System.Collections.Generic; +using System; using System.Threading; -using System.Diagnostics; namespace Barotrauma.Sounds { @@ -17,7 +15,7 @@ namespace Barotrauma.Sounds public SoundSourcePool(int sourceCount = SoundManager.SOURCE_COUNT) { - int alError = Al.NoError; + int alError; ALSources = new uint[sourceCount]; for (int i = 0; i < sourceCount; i++) @@ -83,7 +81,7 @@ namespace Barotrauma.Sounds class SoundChannel : IDisposable { private const int STREAM_BUFFER_SIZE = 8820; - private short[] streamShortBuffer; + private readonly short[] streamShortBuffer; private string debugName = "SoundChannel"; @@ -312,12 +310,12 @@ namespace Barotrauma.Sounds if (ALSourceIndex < 0) { return; } - if (!IsPlaying) return; + if (!IsPlaying) { return; } if (!IsStream) { uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); - int playbackPos; Al.GetSourcei(alSource, Al.SampleOffset, out playbackPos); + Al.GetSourcei(alSource, Al.SampleOffset, out int playbackPos); int alError = Al.GetError(); if (alError != Al.NoError) { @@ -379,7 +377,7 @@ namespace Barotrauma.Sounds if (!IsStream) { - int playbackPos; Al.GetSourcei(alSource, Al.SampleOffset, out playbackPos); + Al.GetSourcei(alSource, Al.SampleOffset, out int playbackPos); int alError = Al.GetError(); if (alError != Al.NoError) { @@ -390,7 +388,7 @@ namespace Barotrauma.Sounds } else { - float retVal = -1.0f; + float retVal; Monitor.Enter(mutex); retVal = streamAmplitude; Monitor.Exit(mutex); @@ -432,8 +430,8 @@ namespace Barotrauma.Sounds private bool reachedEndSample; private int queueStartIndex; private readonly uint[] streamBuffers; - private uint[] unqueuedBuffers; - private float[] streamBufferAmplitudes; + private readonly uint[] unqueuedBuffers; + private readonly float[] streamBufferAmplitudes; public int StreamSeekPos { @@ -448,18 +446,17 @@ namespace Barotrauma.Sounds } } - private object mutex; + private readonly object mutex; public bool IsPlaying { get { - if (ALSourceIndex < 0) return false; - if (IsStream && !reachedEndSample) return true; - int state; + if (ALSourceIndex < 0) { return false; } + if (IsStream && !reachedEndSample) { return true; } uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); - if (!Al.IsSource(alSource)) return false; - Al.GetSourcei(alSource, Al.SourceState, out state); + if (!Al.IsSource(alSource)) { return false; } + Al.GetSourcei(alSource, Al.SourceState, out int state); int alError = Al.GetError(); if (alError != Al.NoError) { @@ -710,8 +707,7 @@ namespace Barotrauma.Sounds { uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); - int state; - Al.GetSourcei(alSource, Al.SourceState, out state); + Al.GetSourcei(alSource, Al.SourceState, out int state); bool playing = state == Al.Playing; int alError = Al.GetError(); if (alError != Al.NoError) @@ -719,8 +715,7 @@ namespace Barotrauma.Sounds throw new Exception("Failed to determine playing state from streamed source: " + debugName + ", " + Al.GetErrorString(alError)); } - int unqueuedBufferCount; - Al.GetSourcei(alSource, Al.BuffersProcessed, out unqueuedBufferCount); + Al.GetSourcei(alSource, Al.BuffersProcessed, out int unqueuedBufferCount); alError = Al.GetError(); if (alError != Al.NoError) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs index 7c52cd028..8e84334b9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs @@ -1,10 +1,8 @@ -using Barotrauma.IO; -using Barotrauma.Networking; +using Barotrauma.Networking; using Concentus.Structs; using Microsoft.Xna.Framework; using OpenAL; using System; -using System.Collections.Generic; namespace Barotrauma.Sounds { @@ -26,12 +24,12 @@ namespace Barotrauma.Sounds } } - private VoipQueue queue; + private readonly VoipQueue queue; private int bufferID = 0; private SoundChannel soundChannel; - private OpusDecoder decoder; + private readonly OpusDecoder decoder; public bool UseRadioFilter; public bool UseMuffleFilter; @@ -39,11 +37,11 @@ namespace Barotrauma.Sounds public float Near { get; private set; } public float Far { get; private set; } - private BiQuad[] muffleFilters = new BiQuad[] + private readonly BiQuad[] muffleFilters = new BiQuad[] { new LowpassFilter(VoipConfig.FREQUENCY, 800) }; - private BiQuad[] radioFilters = new BiQuad[] + private readonly BiQuad[] radioFilters = new BiQuad[] { new BandpassFilter(VoipConfig.FREQUENCY, 2000) }; @@ -101,13 +99,14 @@ namespace Barotrauma.Sounds public void ApplyFilters(short[] buffer, int readSamples) { + float finalGain = gain * GameSettings.CurrentConfig.Audio.VoiceChatVolume; for (int i = 0; i < readSamples; i++) { float fVal = ShortToFloat(buffer[i]); - if (gain * GameSettings.CurrentConfig.Audio.VoiceChatVolume > 1.0f) //TODO: take distance into account? + if (finalGain > 1.0f) //TODO: take distance into account? { - fVal = Math.Clamp(fVal * gain * GameSettings.CurrentConfig.Audio.VoiceChatVolume, -1f, 1f); + fVal = Math.Clamp(fVal * finalGain, -1f, 1f); } if (UseMuffleFilter) diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 01ebb078b..859a9680e 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.20.6.0 + 0.20.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 81a1c286d..427dd7c3b 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.20.6.0 + 0.20.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index c425d7591..214920372 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.20.6.0 + 0.20.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 1b57d0a81..d973c5701 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.20.6.0 + 0.20.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index f9fb6ebfa..4b6b7d42f 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.20.6.0 + 0.20.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Fabricator.cs index c3a383431..8a99abc2a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Fabricator.cs @@ -9,11 +9,12 @@ namespace Barotrauma.Items.Components public void ServerEventRead(IReadMessage msg, Client c) { uint recipeHash = msg.ReadUInt32(); - + int amountToFabricate = msg.ReadRangedInteger(1, MaxAmountToFabricate); item.CreateServerEvent(this); if (!item.CanClientAccess(c)) { return; } + AmountToFabricate = amountToFabricate; if (recipeHash == 0) { CancelFabricating(c.Character); @@ -24,6 +25,8 @@ namespace Barotrauma.Items.Components if (fabricatedItem != null && fabricatedItem.RecipeHash == recipeHash) { return; } if (recipeHash == 0) { return; } + amountRemaining = AmountToFabricate; + StartFabricating(fabricationRecipes[recipeHash], c.Character); } } @@ -56,6 +59,8 @@ namespace Barotrauma.Items.Components { var componentData = ExtractEventData(extraData); msg.WriteByte((byte)componentData.State); + msg.WriteRangedInteger(AmountToFabricate, 0, MaxAmountToFabricate); + msg.WriteRangedInteger(amountRemaining, 0, MaxAmountToFabricate); msg.WriteSingle(timeUntilReady); uint recipeHash = fabricatedItem?.RecipeHash ?? 0; msg.WriteUInt32(recipeHash); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs index f4063a8d1..e77071639 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs @@ -2,19 +2,18 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; -using System.Text; namespace Barotrauma.Networking { class VoipServer { - private ServerPeer netServer; - private List queues; - private Dictionary lastSendTime; + private readonly ServerPeer netServer; + private readonly List queues; + private readonly Dictionary lastSendTime; public VoipServer(ServerPeer server) { - this.netServer = server; + netServer = server; queues = new List(); lastSendTime = new Dictionary(); } @@ -46,17 +45,19 @@ namespace Barotrauma.Networking } Client sender = clients.Find(c => c.VoipQueue == queue); + if (sender == null) { return; } foreach (Client recipient in clients) { if (recipient == sender) { continue; } - if (!CanReceive(sender, recipient)) { continue; } + if (!CanReceive(sender, recipient, out float distanceFactor)) { continue; } IWriteMessage msg = new WriteOnlyMessage(); msg.WriteByte((byte)ServerPacketHeader.VOICE); msg.WriteByte((byte)queue.QueueID); + msg.WriteRangedSingle(distanceFactor, 0.0f, 1.0f, 8); queue.Write(msg); netServer.Send(msg, recipient.Connection, DeliveryMethod.Unreliable); @@ -64,9 +65,15 @@ namespace Barotrauma.Networking } } - private bool CanReceive(Client sender, Client recipient) + private static bool CanReceive(Client sender, Client recipient, out float distanceFactor) { - if (Screen.Selected != GameMain.GameScreen) { return true; } + if (Screen.Selected != GameMain.GameScreen) + { + distanceFactor = 0.0f; + return true; + } + + distanceFactor = 0.0f; //no-one can hear muted players if (sender.Muted) { return false; } @@ -74,30 +81,47 @@ namespace Barotrauma.Networking bool recipientSpectating = recipient.Character == null || recipient.Character.IsDead; bool senderSpectating = sender.Character == null || sender.Character.IsDead; - //TODO: only allow spectators to hear the voice chat if close enough to the speaker? - - //non-spectators cannot hear spectators - if (senderSpectating && !recipientSpectating) { return false; } - - //both spectating, no need to do radio/distance checks - if (recipientSpectating && senderSpectating) { return true; } - - //spectators can hear non-spectators - if (!senderSpectating && recipientSpectating) { return true; } + //non-spectators cannot hear spectators, and spectators can always hear spectators + if (senderSpectating) + { + return recipientSpectating; + } //sender can't speak if (sender.Character != null && sender.Character.SpeechImpediment >= 100.0f) { return false; } //check if the message can be sent via radio + WifiComponent recipientRadio = null; if (!sender.VoipQueue.ForceLocal && - ChatMessage.CanUseRadio(sender.Character, out WifiComponent senderRadio) && - ChatMessage.CanUseRadio(recipient.Character, out WifiComponent recipientRadio)) + ChatMessage.CanUseRadio(sender.Character, out WifiComponent senderRadio) && + (recipientSpectating || ChatMessage.CanUseRadio(recipient.Character, out recipientRadio))) { - if (recipientRadio.CanReceive(senderRadio)) { return true; } + if (recipientSpectating) + { + if (recipient.SpectatePos == null) { return true; } + distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.SpectatePos.Value) / senderRadio.Range, 0.0f, 1.0f); + return distanceFactor < 1.0f; + } + else if (recipientRadio != null && recipientRadio.CanReceive(senderRadio)) + { + distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.Character.WorldPosition) / senderRadio.Range, 0.0f, 1.0f); + return true; + } } - //otherwise do a distance check - return ChatMessage.GetGarbleAmount(recipient.Character, sender.Character, ChatMessage.SpeakRange) < 1.0f; + if (recipientSpectating) + { + if (recipient.SpectatePos == null) { return true; } + distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.SpectatePos.Value) / ChatMessage.SpeakRange, 0.0f, 1.0f); + return distanceFactor < 1.0f; + } + else + { + //otherwise do a distance check + float garbleAmount = ChatMessage.GetGarbleAmount(recipient.Character, sender.Character, ChatMessage.SpeakRange); + distanceFactor = garbleAmount; + return garbleAmount < 1.0f; + } } } } diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 21f441326..cf612841f 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.20.6.0 + 0.20.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs index 82b3e7591..76a6d12de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs @@ -102,6 +102,8 @@ namespace Barotrauma private bool inDetectable; + public double InDetectableSetTime; + /// /// Should be reset to false each frame and kept indetectable by e.g. a status effect. /// @@ -115,7 +117,8 @@ namespace Barotrauma { inDetectable = value; if (inDetectable) - { + { + InDetectableSetTime = Timing.TotalTime; NeedsUpdate = true; } } @@ -257,9 +260,14 @@ namespace Barotrauma SightRange -= speed * deltaTime * (MaxSightRange / FadeOutTime); } + public bool HasSector() + { + return sectorRad < MathHelper.TwoPi; + } + public bool IsWithinSector(Vector2 worldPosition) { - if (sectorRad >= MathHelper.TwoPi) { return true; } + if (!HasSector()) { return true; } Vector2 diff = worldPosition - WorldPosition; return Math.Abs(MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir))) <= sectorRad * 0.5f; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index f3ac3917c..83e3ca0c4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -1083,7 +1083,7 @@ namespace Barotrauma State = AIState.Idle; return; } - else + else if (!owner.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { SelectedAiTarget = owner.AiTarget; } @@ -2147,7 +2147,6 @@ namespace Barotrauma if (SelectedAiTarget?.Entity == null) { return false; } if (AttackLimb?.attack == null) { return false; } if (damageTarget == null) { return false; } - ActiveAttack = AttackLimb.attack; if (wallTarget != null) { // If the selected target is not the wall target, make the wall target the selected target. @@ -2156,9 +2155,10 @@ namespace Barotrauma { SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, addIfNotFound: true).Priority); State = AIState.Attack; + return true; } } - if (damageTarget == null) { return false; } + ActiveAttack = AttackLimb.attack; if (ActiveAttack.Ranged && ActiveAttack.RequiredAngleToShoot > 0) { Limb referenceLimb = GetLimbToRotate(ActiveAttack); @@ -3810,6 +3810,7 @@ namespace Barotrauma { SelectTarget(door.Item.AiTarget, SelectedTargetMemory.Priority); State = AIState.Attack; + return false; } } } @@ -3881,9 +3882,8 @@ namespace Barotrauma return targetLimb; } - private Character GetOwner(Item item) + private static Character GetOwner(Item item) { - // If the item is held by a character, attack the character instead. var pickable = item.GetComponent(); if (pickable != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs index 7af3858b9..cf8cf19ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -413,28 +413,10 @@ namespace Barotrauma } } } - + private void ApplyTreatment(Affliction affliction, Item item) { - var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction); - bool remove = false; - foreach (ItemComponent ic in item.Components) - { - if (!ic.HasRequiredContainedItems(user: character, addMessage: false)) { continue; } -#if CLIENT - ic.PlaySound(ActionType.OnUse, character); -#endif - ic.WasUsed = true; - ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: character); - if (ic.DeleteOnUse) - { - remove = true; - } - } - if (remove) - { - Entity.Spawner?.AddItemToRemoveQueue(item); - } + item.ApplyTreatment(character, targetCharacter, targetCharacter.CharacterHealth.GetAfflictionLimb(affliction)); } protected override bool CheckObjectiveSpecific() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 3a9a063ac..b617fef5f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -1371,7 +1371,7 @@ namespace Barotrauma Limb head = GetLimb(LimbType.Head); Limb torso = GetLimb(LimbType.Torso); - + Vector2 headDiff = targetHead == null ? diff : targetHead.SimPosition - character.SimPosition; targetMovement = new Vector2(diff.X, 0.0f); TargetDir = headDiff.X > 0.0f ? Direction.Right : Direction.Left; @@ -1386,15 +1386,25 @@ namespace Barotrauma float prevVitality = target.Vitality; bool wasCritical = prevVitality < 0.0f; - + if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code { target.Oxygen += deltaTime * 0.5f; //Stabilize them } float cprBoost = character.GetStatValue(StatTypes.CPRBoost); - + int skill = (int)character.GetSkillLevel("medical"); + + if (GameMain.NetworkMember is not { IsClient: true }) + { + if (cprBoost >= 1f) + { + //prevent the patient from suffocating no matter how fast their oxygen level is dropping + target.Oxygen = Math.Max(target.Oxygen, -10.0f); + } + } + //pump for 15 seconds (cprAnimTimer 0-15), then do mouth-to-mouth for 2 seconds (cprAnimTimer 15-17) if (cprAnimTimer > 15.0f && targetHead != null && head != null) { @@ -1405,23 +1415,15 @@ namespace Barotrauma torso.PullJointEnabled = true; //Serverside code - if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) + if (GameMain.NetworkMember is not { IsClient: true }) { if (target.Oxygen < -10.0f) { - if (cprBoost >= 1f) - { - //prevent the patient from suffocating no matter how fast their oxygen level is dropping - target.Oxygen = Math.Max(target.Oxygen, -10.0f); - } - else - { - //stabilize the oxygen level but don't allow it to go positive and revive the character yet - float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill; - stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax); - character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required - if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we - } + //stabilize the oxygen level but don't allow it to go positive and revive the character yet + float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill; + stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax); + character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required + if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 5238017dc..280300c94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -25,6 +25,8 @@ namespace Barotrauma FriendlyNPC = 3 } + public readonly record struct TalentResistanceIdentifier(Identifier ResistanceIdentifier, Identifier TalentIdentifier); + partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync { public readonly static List CharacterList = new List(); @@ -347,7 +349,7 @@ namespace Barotrauma private readonly Dictionary itemSelectedDurations = new Dictionary(); private double itemSelectedTime; - public float InvisibleTimer; + public float InvisibleTimer { get; set; } public readonly CharacterPrefab Prefab; @@ -1372,7 +1374,28 @@ namespace Barotrauma tags.RemoveWhere(t => t.StartsWith("variant")); tags.Add($"variant{headId.Value}".ToIdentifier()); } + var oldHeadInfo = Info.Head; Info.RecreateHead(tags.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); + if (hairIndex == -1) + { + Info.Head.HairIndex = oldHeadInfo.HairIndex; + } + if (beardIndex == -1) + { + Info.Head.BeardIndex = oldHeadInfo.BeardIndex; + } + if (moustacheIndex == -1) + { + Info.Head.MoustacheIndex = oldHeadInfo.MoustacheIndex; + } + if (faceAttachmentIndex == -1) + { + Info.Head.FaceAttachmentIndex = oldHeadInfo.FaceAttachmentIndex; + } + Info.Head.SkinColor = oldHeadInfo.SkinColor; + Info.Head.HairColor = oldHeadInfo.HairColor; + Info.Head.FacialHairColor = oldHeadInfo.FacialHairColor; + Info.CheckColors(); #if CLIENT head.RecreateSprites(); #endif @@ -3050,7 +3073,8 @@ namespace Barotrauma ApplyStatusEffects(AnimController.InWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); ApplyStatusEffects(ActionType.OnActive, deltaTime); - if (aiTarget != null) + //wait 0.1 seconds so status effects that continuously set InDetectable to true can keep the character InDetectable + if (aiTarget != null && Timing.TotalTime > aiTarget.InDetectableSetTime + 0.1f) { aiTarget.InDetectable = false; } @@ -5087,25 +5111,48 @@ namespace Barotrauma return abilityFlags.HasFlag(abilityFlag) || CharacterHealth.HasFlag(abilityFlag); } - private readonly Dictionary abilityResistances = new Dictionary(); - + private readonly Dictionary abilityResistances = new(); + public float GetAbilityResistance(AfflictionPrefab affliction) { - return abilityResistances.TryGetValue(affliction.Identifier, out float value) ? value : abilityResistances.TryGetValue(affliction.AfflictionType, out float typeValue) ? typeValue : 1f; + float resistance = 0f; + bool hadResistance = false; + + foreach (var (key, value) in abilityResistances) + { + if (key.ResistanceIdentifier == affliction.AfflictionType || + key.ResistanceIdentifier == affliction.Identifier) + { + resistance += value; + hadResistance = true; + } + } + + return hadResistance ? resistance : 1f; } - public void ChangeAbilityResistance(Identifier resistanceId, float value) + public void ChangeAbilityResistance(TalentResistanceIdentifier identifier, float value) { - if (abilityResistances.ContainsKey(resistanceId)) + if (!MathUtils.IsValid(value)) { - abilityResistances[resistanceId] *= value; +#if DEBUG + DebugConsole.ThrowError($"Attempted to set ability resistance to an invalid value ({value})\n" + Environment.StackTrace.CleanupStackTrace()); +#endif + return; + } + + if (abilityResistances.ContainsKey(identifier)) + { + abilityResistances[identifier] *= value; } else { - abilityResistances.Add(resistanceId, value); + abilityResistances.Add(identifier, value); } } + public void RemoveAbilityResistance(TalentResistanceIdentifier identifier) => abilityResistances.Remove(identifier); + /// /// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 24a96e3fe..789b1616e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -63,27 +63,34 @@ namespace Barotrauma public readonly CharacterInfo CharacterInfo; public readonly HeadPreset Preset; - private int hairIndex; + public int HairIndex { get; set; } - public int HairIndex + private int? hairWithHatIndex; + + public void SetHairWithHatIndex() { - get => hairIndex; - set + if (CharacterInfo.Hairs is null) { - hairIndex = value; - if (CharacterInfo.Hairs is null) + if (HairIndex == -1) { - HairWithHatIndex = value; - return; +#if DEBUG + DebugConsole.ThrowError("Setting \"hairWithHatIndex\" before \"Hairs\" are defined!"); +#else + DebugConsole.AddWarning("Setting \"hairWithHatIndex\" before \"Hairs\" are defined!"); +#endif } - HairWithHatIndex = HairElement?.GetAttributeInt("replacewhenwearinghat", hairIndex) ?? -1; - if (HairWithHatIndex < 0 || HairWithHatIndex >= CharacterInfo.Hairs.Count) + hairWithHatIndex = HairIndex; + } + else + { + hairWithHatIndex = HairElement?.GetAttributeInt("replacewhenwearinghat", HairIndex) ?? -1; + if (hairWithHatIndex < 0 || hairWithHatIndex >= CharacterInfo.Hairs.Count) { - HairWithHatIndex = hairIndex; + hairWithHatIndex = HairIndex; } } } - public int HairWithHatIndex { get; private set; } + public int BeardIndex; public int MoustacheIndex; public int FaceAttachmentIndex; @@ -99,26 +106,29 @@ namespace Barotrauma get { if (CharacterInfo.Hairs == null) { return null; } - if (hairIndex >= CharacterInfo.Hairs.Count) + if (HairIndex >= CharacterInfo.Hairs.Count) { - DebugConsole.AddWarning($"Hair index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {hairIndex})"); + DebugConsole.AddWarning($"Hair index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {HairIndex})"); } - return CharacterInfo.Hairs.ElementAtOrDefault(hairIndex); + return CharacterInfo.Hairs.ElementAtOrDefault(HairIndex); } } public ContentXElement HairWithHatElement { get { - if (CharacterInfo.Hairs == null) { return null; } - if (HairWithHatIndex >= CharacterInfo.Hairs.Count) + if (hairWithHatIndex == null) { - DebugConsole.AddWarning($"Hair with hat index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {HairWithHatIndex})"); + SetHairWithHatIndex(); } - return CharacterInfo.Hairs.ElementAtOrDefault(HairWithHatIndex); + if (CharacterInfo.Hairs == null) { return null; } + if (hairWithHatIndex >= CharacterInfo.Hairs.Count) + { + DebugConsole.AddWarning($"Hair with hat index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {hairWithHatIndex})"); + } + return CharacterInfo.Hairs.ElementAtOrDefault(hairWithHatIndex.Value); } } - public ContentXElement BeardElement { get @@ -711,7 +721,7 @@ namespace Barotrauma private bool IsColorValid(in Color clr) => clr.R != 0 || clr.G != 0 || clr.B != 0; - private void CheckColors() + public void CheckColors() { if (!IsColorValid(Head.HairColor)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs index e51a8acd3..12f81e8ed 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs @@ -27,6 +27,14 @@ namespace Barotrauma get { return _strength; } set { + if (!MathUtils.IsValid(value)) + { +#if DEBUG + DebugConsole.ThrowError($"Attempted to set an affliction to an invalid strength ({value})\n" + Environment.StackTrace.CleanupStackTrace()); +#endif + return; + } + if (_nonClampedStrength < 0 && value > 0) { _nonClampedStrength = value; @@ -440,9 +448,9 @@ namespace Barotrauma { statusEffect.Apply(type, deltaTime, characterHealth.Character, targetLimb); } - if (targetLimb != null && statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs)) + if (characterHealth?.Character?.AnimController?.Limbs != null && statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs)) { - statusEffect.Apply(type, deltaTime, targetLimb.character, targets: targetLimb.character.AnimController.Limbs); + statusEffect.Apply(type, deltaTime, characterHealth.Character, targets: characterHealth.Character.AnimController.Limbs); } if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) || statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index cd6e25f64..523835530 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -405,45 +405,53 @@ namespace Barotrauma var root = doc.Root.FromPackage(pathToAppendage.ContentPackage); var limbElements = root.GetChildElements("limb").ToDictionary(e => e.GetAttributeString("id", null), e => e); + //the IDs may need to be offset if the character has other extra appendages (e.g. from gene splicing) + //that take up the IDs of this appendage + int idOffset = 0; foreach (var jointElement in root.GetChildElements("joint")) { - if (limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out ContentXElement limbElement)) + if (!limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out ContentXElement limbElement)) { continue; } + + var jointParams = new RagdollParams.JointParams(jointElement, ragdoll.RagdollParams); + Limb attachLimb = null; + if (matchingAffliction.AttachLimbId > -1) { - var jointParams = new RagdollParams.JointParams(jointElement, ragdoll.RagdollParams); - Limb attachLimb = null; - if (matchingAffliction.AttachLimbId > -1) - { - attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId); - } - else if (matchingAffliction.AttachLimbName != null) - { - attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName); - } - else if (matchingAffliction.AttachLimbType != LimbType.None) - { - attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType); - } - if (attachLimb == null) - { - attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1); - } - if (attachLimb != null) - { - jointParams.Limb1 = attachLimb.Params.ID; - var appendageLimbParams = new RagdollParams.LimbParams(limbElement, ragdoll.RagdollParams) - { - // Ensure that we have a valid id for the new limb - ID = ragdoll.Limbs.Length - }; - jointParams.Limb2 = appendageLimbParams.ID; - Limb huskAppendage = new Limb(ragdoll, character, appendageLimbParams); - huskAppendage.body.Submarine = character.Submarine; - huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation); - ragdoll.AddLimb(huskAppendage); - ragdoll.AddJoint(jointParams); - appendage.Add(huskAppendage); - } + attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId); } + else if (matchingAffliction.AttachLimbName != null) + { + attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName); + } + else if (matchingAffliction.AttachLimbType != LimbType.None) + { + attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType); + } + if (attachLimb == null) + { + attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1); + } + if (attachLimb != null) + { + jointParams.Limb1 = attachLimb.Params.ID; + //the joint attaches to a limb outside the character's normal limb count = to another part of the appendage + // -> if the appendage's IDs have been offset, we need to take that into account to attach to the correct limb + if (jointParams.Limb1 >= ragdoll.RagdollParams.Limbs.Count) + { + jointParams.Limb1 += idOffset; + } + var appendageLimbParams = new RagdollParams.LimbParams(limbElement, ragdoll.RagdollParams); + if (idOffset == 0) + { + idOffset = ragdoll.Limbs.Length - appendageLimbParams.ID; + } + jointParams.Limb2 = appendageLimbParams.ID = ragdoll.Limbs.Length; + Limb huskAppendage = new Limb(ragdoll, character, appendageLimbParams); + huskAppendage.body.Submarine = character.Submarine; + huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation); + ragdoll.AddLimb(huskAppendage); + ragdoll.AddJoint(jointParams); + appendage.Add(huskAppendage); + } } return appendage; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 8e5d0bbfd..93d2ecc8c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -887,7 +887,7 @@ namespace Barotrauma //clamp above 0.1 (no amount of oxygen low resistance should keep the character alive indefinitely) float decreaseSpeed = Math.Max(0.1f, 1f - oxygenlowResistance); //the character dies of oxygen deprivation in 100 seconds after losing consciousness - OxygenAmount = MathHelper.Clamp(OxygenAmount - decreaseSpeed * deltaTime, -100.0f, 100.0f); + OxygenAmount = MathHelper.Clamp(OxygenAmount - decreaseSpeed * deltaTime, -100.0f, 100.0f); } else { @@ -895,15 +895,21 @@ namespace Barotrauma float increaseSpeed = 10.0f; decreaseSpeed *= (1f - oxygenlowResistance); increaseSpeed *= (1f + oxygenlowResistance); - - float holdBreathMultiplier = 1f + GetStatValue(StatTypes.HoldBreathMultiplier); - decreaseSpeed *= holdBreathMultiplier; - OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? decreaseSpeed : increaseSpeed), -100.0f, 100.0f); + float holdBreathMultiplier = Character.GetStatValue(StatTypes.HoldBreathMultiplier); + if (holdBreathMultiplier <= -1.0f) + { + OxygenAmount = -100.0f; + } + else + { + decreaseSpeed /= 1.0f + Character.GetStatValue(StatTypes.HoldBreathMultiplier); + OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? decreaseSpeed : increaseSpeed), -100.0f, 100.0f); + } } UpdateOxygenProjSpecific(prevOxygen, deltaTime); } - + partial void UpdateOxygenProjSpecific(float prevOxygen, float deltaTime); partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs index e934eb2aa..0b350c514 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs index 83d56c28d..f2e238080 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs @@ -1,5 +1,5 @@ -using System.Collections.Immutable; -using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework; +using System.Collections.Immutable; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs index 4161a881e..0cd2b4857 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs @@ -1,7 +1,4 @@ -using Microsoft.Xna.Framework; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityGainSimultaneousSkill : CharacterAbility { @@ -15,6 +12,10 @@ namespace Barotrauma.Abilities skillIdentifier = abilityElement.GetAttributeIdentifier("skillidentifier", ""); ignoreAbilitySkillGain = abilityElement.GetAttributeBool("ignoreabilityskillgain", true); targetAllies = abilityElement.GetAttributeBool("targetallies", false); + if (skillIdentifier.IsEmpty) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: skill identifier not defined."); + } } protected override void ApplyEffect(AbilityObject abilityObject) @@ -23,13 +24,12 @@ namespace Barotrauma.Abilities { if (ignoreAbilitySkillGain && abilitySkillGain.GainedFromAbility) { return; } Identifier identifier = skillIdentifier == "inherit" ? abilitySkillGain.SkillIdentifier : skillIdentifier; - if (targetAllies) { - foreach (Character character in Character.GetFriendlyCrew(Character)) + foreach (Character otherCharacter in Character.GetFriendlyCrew(Character)) { - if (character == Character) { continue; } - Character.Info?.IncreaseSkillLevel(identifier, abilitySkillGain.Value, gainedFromAbility: true); + if (otherCharacter == Character) { continue; } + otherCharacter.Info?.IncreaseSkillLevel(identifier, abilitySkillGain.Value, gainedFromAbility: true); } } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs index 9dcab6bb6..13bae5e96 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs @@ -1,6 +1,4 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityGiveAffliction : CharacterAbility { @@ -18,7 +16,7 @@ namespace Barotrauma.Abilities if (afflictionId.IsEmpty) { - DebugConsole.ThrowError("Error in CharacterAbilityGiveAffliction - affliction identifier not set."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveAffliction - affliction identifier not set."); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs index e56bee86c..a315a3328 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs @@ -1,6 +1,4 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityGiveFlag : CharacterAbility { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs index 40148524c..a45e92b1d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs @@ -1,6 +1,4 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityGiveMoney : CharacterAbility { @@ -13,6 +11,11 @@ namespace Barotrauma.Abilities { amount = abilityElement.GetAttributeInt("amount", 0); scalingStatIdentifier = abilityElement.GetAttributeIdentifier("scalingstatidentifier", Identifier.Empty); + + if (amount == 0) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveMoney - amount of money set to 0."); + } } private void ApplyEffectSpecific(Character targetCharacter) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index bd54f9413..b62f56296 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -1,6 +1,4 @@ -using System; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { public enum PermanentStatPlaceholder { @@ -28,6 +26,10 @@ namespace Barotrauma.Abilities public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty); + if (statIdentifier.IsEmpty) + { + DebugConsole.ThrowError($"Error in talent \"{CharacterTalent.DebugIdentifier}\" - stat identifier not defined."); + } string statTypeName = abilityElement.GetAttributeString("stattype", string.Empty); statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, CharacterTalent.DebugIdentifier); value = abilityElement.GetAttributeFloat("value", 0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveReputation.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveReputation.cs index 37fb09935..6d3777d53 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveReputation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveReputation.cs @@ -11,6 +11,14 @@ namespace Barotrauma.Abilities { factionIdentifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty); amount = abilityElement.GetAttributeFloat("amount", 0f); + if (factionIdentifier.IsEmpty) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, faction identifier not defined."); + } + if (amount == 0) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of reputation to give is 0."); + } } protected override void ApplyEffect() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs index 347f69a25..9df7fc87b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs @@ -1,6 +1,4 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityGiveResistance : CharacterAbility { @@ -10,17 +8,23 @@ namespace Barotrauma.Abilities public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { resistanceId = abilityElement.GetAttributeIdentifier("resistanceid", abilityElement.GetAttributeIdentifier("resistance", Identifier.Empty)); - multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); // rename this to resistance for consistency + multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); if (resistanceId.IsEmpty) { DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set."); } + if (MathUtils.NearlyEqual(multiplier, 1)) + { + DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - multiplier set to 1, which will do nothing."); + } + } public override void InitializeAbility(bool addingFirstTime) { - Character.ChangeAbilityResistance(resistanceId, multiplier); + TalentResistanceIdentifier identifier = new(resistanceId, CharacterTalent.Prefab.Identifier); + Character.ChangeAbilityResistance(identifier, multiplier); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs index a97ec2ee4..4cc9d37a3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs @@ -1,6 +1,4 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityGiveStat : CharacterAbility { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs index 1eed1afae..e61e3981b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs @@ -1,7 +1,4 @@ -using Barotrauma.Extensions; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityGiveTalentPoints : CharacterAbility { @@ -9,7 +6,11 @@ namespace Barotrauma.Abilities public CharacterAbilityGiveTalentPoints(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { - amount = abilityElement.GetAttributeInt("amount", 0); + amount = abilityElement.GetAttributeInt("amount", 0); + if (amount == 0) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0."); + } } public override void InitializeAbility(bool addingFirstTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs index 130b5b988..2b4dd4cac 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs @@ -9,6 +9,10 @@ namespace Barotrauma.Abilities public CharacterAbilityGiveTalentPointsToAllies(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { amount = abilityElement.GetAttributeInt("amount", 0); + if (amount == 0) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0."); + } } public override void InitializeAbility(bool addingFirstTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs index cee2198ff..2e1816bd4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs @@ -1,6 +1,4 @@ using Barotrauma.Extensions; -using Microsoft.Xna.Framework; -using System.Xml.Linq; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityMarkAsLooted.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityMarkAsLooted.cs index c9460ff46..45ddb19fb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityMarkAsLooted.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityMarkAsLooted.cs @@ -6,6 +6,10 @@ namespace Barotrauma.Abilities public CharacterAbilityMarkAsLooted(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { identifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty); + if (identifier.IsEmpty) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, identifier is empty in {nameof(CharacterAbilityMarkAsLooted)}."); + } } protected override void ApplyEffect(AbilityObject abilityObject) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs index 52f47c471..ff7ac0995 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs @@ -1,7 +1,4 @@ -using System; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityModifyFlag : CharacterAbility { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs index ed5a5a35f..3c1ec2272 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs @@ -1,23 +1,25 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityModifyResistance : CharacterAbility { private readonly Identifier resistanceId; - private readonly float resistance; + private readonly float multiplier; bool lastState; public override bool AllowClientSimulation => true; // should probably be split to different classes public CharacterAbilityModifyResistance(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { - resistanceId = abilityElement.GetAttributeIdentifier("resistanceid", ""); - resistance = abilityElement.GetAttributeFloat("resistance", 1f); + resistanceId = abilityElement.GetAttributeIdentifier("resistanceid", abilityElement.GetAttributeIdentifier("resistance", Identifier.Empty)); + multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); if (resistanceId.IsEmpty) { - DebugConsole.ThrowError("Error in CharacterAbilityModifyResistance - resistance identifier not set."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - resistance identifier not set in {nameof(CharacterAbilityModifyResistance)}."); + } + if (MathUtils.NearlyEqual(multiplier, 1.0f)) + { + DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - resistance set to 1, which will do nothing."); } } @@ -25,7 +27,15 @@ namespace Barotrauma.Abilities { if (conditionsMatched != lastState) { - Character.ChangeAbilityResistance(resistanceId, conditionsMatched ? resistance : 1 / resistance); + TalentResistanceIdentifier identifier = new(resistanceId, CharacterTalent.Prefab.Identifier); + if (conditionsMatched) + { + Character.ChangeAbilityResistance(identifier, multiplier); + } + else + { + Character.RemoveAbilityResistance(identifier); + } lastState = conditionsMatched; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStat.cs index c7f792475..a1f69c328 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStat.cs @@ -1,6 +1,4 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityModifyStat : CharacterAbility { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToLevel.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToLevel.cs index a3141b037..16979f552 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToLevel.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToLevel.cs @@ -1,5 +1,4 @@ using Microsoft.Xna.Framework; -using System.Xml.Linq; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs index 26cec9b48..b02b85e1d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs @@ -1,5 +1,4 @@ using System.Linq; -using System.Xml.Linq; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs index 6970c2e6d..57ed31b3b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs @@ -1,6 +1,4 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityModifyValue : CharacterAbility { @@ -11,6 +9,10 @@ namespace Barotrauma.Abilities { addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f); multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); + if (MathUtils.NearlyEqual(addedValue, 0.0f) && MathUtils.NearlyEqual(multiplyValue, 1.0f)) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityModifyValue)} - added value is 0 and multiplier is 1, the ability will do nothing."); + } } protected override void ApplyEffect(AbilityObject abilityObject) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs index 10c1ddcd1..ba7ef06eb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs @@ -1,6 +1,4 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityPutItem : CharacterAbility { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs index df57f5f1c..3e85a16dd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs @@ -9,7 +9,11 @@ namespace Barotrauma.Abilities public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { - statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty); + statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty); + if (statIdentifier.IsEmpty) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityResetPermanentStat)} - statIdentifier is empty."); + } } protected override void ApplyEffect(AbilityObject abilityObject) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs index 21d679bd2..4ed14321d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs @@ -1,7 +1,4 @@ -using Microsoft.Xna.Framework; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityRevive : CharacterAbility { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs index a8da25c74..3953cce9f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs @@ -11,6 +11,10 @@ namespace Barotrauma.Abilities { identifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty); value = abilityElement.GetAttributeInt("value", 0); + if (identifier.IsEmpty) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilitySetMetadataInt)} - identifier is empty."); + } } public override void InitializeAbility(bool addingFirstTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs index 4d6647f55..e3d1676d0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Xml.Linq; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs index 9a67d1fd3..87a63db20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs index e212b3224..c7a549aa5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs @@ -1,6 +1,4 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityApprenticeship : CharacterAbility { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs index 745cb706e..4bc16e754 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs @@ -1,20 +1,17 @@ using System; using System.Linq; -using System.Xml.Linq; namespace Barotrauma.Abilities { class CharacterAbilityAtmosMachine : CharacterAbility { private readonly float addedValue; - private readonly float multiplyValue; private readonly Identifier[] tags; private readonly int maxMultiplyCount; public CharacterAbilityAtmosMachine(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f); - multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); tags = abilityElement.GetAttributeIdentifierArray("tags", Array.Empty()); maxMultiplyCount = abilityElement.GetAttributeInt("maxmultiplycount", int.MaxValue); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs index 75aa86835..77d765a57 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs @@ -1,11 +1,8 @@ -using System.Collections.Generic; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityBountyHunter : CharacterAbility { - private float vitalityPercentage; + private readonly float vitalityPercentage; public CharacterAbilityBountyHunter(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs index c3c99c080..29f0cf0cb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs index 24b2a02e2..042edb00e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs @@ -1,7 +1,4 @@ -using Microsoft.Xna.Framework; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityMultitasker : CharacterAbility { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs index c9a160923..5f8b94591 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs @@ -1,12 +1,10 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityPsychoClown : CharacterAbility { - private StatTypes statType; - private float maxValue; - private string afflictionIdentifier; + private readonly StatTypes statType; + private readonly float maxValue; + private readonly string afflictionIdentifier; private float lastValue = 0f; public override bool AllowClientSimulation => true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs index 85ef28d54..75d474784 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs @@ -1,8 +1,5 @@ using Barotrauma.Items.Components; -using Microsoft.Xna.Framework; -using System; using System.Collections.Generic; -using System.Xml.Linq; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs index 96bdd50a9..f35fd90e9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs @@ -1,8 +1,8 @@ #nullable enable +using Barotrauma.Extensions; using System.Collections.Generic; using System.Collections.Immutable; -using Barotrauma.Extensions; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs index 2702f649f..dc365c4c1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -66,8 +66,8 @@ namespace Barotrauma IEnumerable blockingSubTrees = tree.TalentSubTrees.Where(tst => tst.BlockedTrees.Contains(targetTree.Identifier)), requiredSubTrees = tree.TalentSubTrees.Where(tst => targetTree.RequiredTrees.Contains(tst.Identifier)); - return requiredSubTrees.All(tst => tst.IsCompleted(selectedTalents)) && // check if we meet requirements - !blockingSubTrees.Any(tst => tst.HasAnyTalent(selectedTalents)); // check if any other talent trees are blocking this one + return requiredSubTrees.All(tst => tst.HasEnoughTalents(selectedTalents)) && // check if we meet requirements + !blockingSubTrees.Any(tst => tst.HasAnyTalent(selectedTalents) && !tst.HasMaxTalents(selectedTalents)); // check if any other talent trees are blocking this one } // i hate this function - markus @@ -128,16 +128,26 @@ namespace Barotrauma public static bool IsViableTalentForCharacter(Character character, Identifier talentIdentifier, IReadOnlyCollection selectedTalents) { if (character?.Info?.Job.Prefab == null) { return false; } - if (character.Info.GetTotalTalentPoints() - selectedTalents.Count <= 0) { return false; } - if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; } foreach (var subTree in talentTree!.TalentSubTrees) { + if (subTree.AllTalentIdentifiers.Contains(talentIdentifier) && subTree.HasMaxTalents(selectedTalents)) { return false; } + foreach (var talentOptionStage in subTree.TalentOptionStages) { - bool hasTalentInThisTier = talentOptionStage.HasEnoughTalents(selectedTalents); + if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier)) + { + return TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents); + } + bool optionStageCompleted = talentOptionStage.HasEnoughTalents(selectedTalents); + if (!optionStageCompleted) + { + break; + } + + /*bool hasTalentInThisTier = talentOptionStage.HasMaxTalents(selectedTalents); if (!hasTalentInThisTier) { if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier)) @@ -145,7 +155,7 @@ namespace Barotrauma return TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents); } break; - } + }*/ } } @@ -196,7 +206,8 @@ namespace Barotrauma public readonly ImmutableHashSet RequiredTrees; public readonly ImmutableHashSet BlockedTrees; - public bool IsCompleted(IReadOnlyCollection talents) => TalentOptionStages.All(option => option.HasEnoughTalents(talents)); + public bool HasEnoughTalents(IReadOnlyCollection talents) => TalentOptionStages.All(option => option.HasEnoughTalents(talents)); + public bool HasMaxTalents(IReadOnlyCollection talents) => TalentOptionStages.All(option => option.HasMaxTalents(talents)); public bool HasAnyTalent(IReadOnlyCollection talents) => TalentOptionStages.Any(option => option.HasSelectedTalent(talents)); public TalentSubTree(ContentXElement subTreeElement) @@ -228,6 +239,13 @@ namespace Barotrauma public IEnumerable TalentIdentifiers => talentIdentifiers; + /// + /// How many talents need to be unlocked to consider this tree completed + /// + public readonly int RequiredTalents; + /// + /// How many talents can be unlocked in total + /// public readonly int MaxChosenTalents; /// @@ -236,8 +254,9 @@ namespace Barotrauma /// public readonly Dictionary> ShowCaseTalents = new Dictionary>(); - public bool HasEnoughTalents(CharacterInfo character) => CountMatchingTalents(character.UnlockedTalents) >= MaxChosenTalents; - public bool HasEnoughTalents(IReadOnlyCollection selectedTalents) => CountMatchingTalents(selectedTalents) >= MaxChosenTalents; + public bool HasEnoughTalents(CharacterInfo character) => CountMatchingTalents(character.UnlockedTalents) >= RequiredTalents; + public bool HasEnoughTalents(IReadOnlyCollection selectedTalents) => CountMatchingTalents(selectedTalents) >= RequiredTalents; + public bool HasMaxTalents(IReadOnlyCollection selectedTalents) => CountMatchingTalents(selectedTalents) >= MaxChosenTalents; // No LINQ public bool HasSelectedTalent(IReadOnlyCollection selectedTalents) @@ -267,10 +286,15 @@ namespace Barotrauma public TalentOption(ContentXElement talentOptionsElement, Identifier debugIdentifier) { - MaxChosenTalents = talentOptionsElement.GetAttributeInt("maxchosentalents", 1); + MaxChosenTalents = talentOptionsElement.GetAttributeInt(nameof(MaxChosenTalents), 1); + RequiredTalents = talentOptionsElement.GetAttributeInt(nameof(RequiredTalents), MaxChosenTalents); + + if (RequiredTalents > MaxChosenTalents) + { + DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - MaxChosenTalents is larger than RequiredTalents."); + } HashSet identifiers = new HashSet(); - foreach (ContentXElement talentOptionElement in talentOptionsElement.Elements()) { Identifier elementName = talentOptionElement.Name.ToIdentifier(); @@ -293,6 +317,15 @@ namespace Barotrauma } talentIdentifiers = identifiers.ToImmutableHashSet(); + + if (RequiredTalents > talentIdentifiers.Count) + { + DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - completing a stage of the tree requires more talents than there are in the stage."); + } + if (MaxChosenTalents > talentIdentifiers.Count) + { + DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - maximum number of talents to choose is larger than the number of talents."); + } } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs index bb8b8e44d..870a7ee9c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs @@ -22,7 +22,7 @@ namespace Barotrauma [Serialize("", IsPropertySaveable.Yes)] public Identifier OrderTargetTag { get; set; } - [Serialize(OrderPriority.Top, IsPropertySaveable.Yes)] + [Serialize(OrderPriority.Any, IsPropertySaveable.Yes)] public OrderPriority Priority { get; set; } public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UIHighlightAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UIHighlightAction.cs index b990fdf66..3182cfae9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UIHighlightAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UIHighlightAction.cs @@ -43,6 +43,9 @@ partial class UIHighlightAction : EventAction [Serialize(true, IsPropertySaveable.Yes)] public bool Bounce { get; set; } + [Serialize(false, IsPropertySaveable.Yes)] + public bool HighlightMultiple { get; set; } + private bool isFinished; public UIHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 481a4a40a..dee7f7b0f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -207,25 +207,23 @@ namespace Barotrauma level.StartLocation.Connections.ForEach(c => c.Locked = false); } } - - AddChildEvents(initialEventSet); - - void AddChildEvents(EventSet eventSet) + } + RegisterNonRepeatableChildEvents(initialEventSet); + void RegisterNonRepeatableChildEvents(EventSet eventSet) + { + if (eventSet == null) { return; } + if (eventSet.OncePerLevel) { - if (eventSet == null) { return; } - if (eventSet.OncePerOutpost) + foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs)) { - foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs)) - { - nonRepeatableEvents.Add(ep); - } - } - foreach (EventSet childSet in eventSet.ChildSets) - { - AddChildEvents(childSet); + nonRepeatableEvents.Add(ep); } } - } + foreach (EventSet childSet in eventSet.ChildSets) + { + RegisterNonRepeatableChildEvents(childSet); + } + } } PreloadContent(GetFilesToPreload()); @@ -374,26 +372,18 @@ namespace Barotrauma /// public void RegisterEventHistory() { - if (level?.LevelData != null) + if (level?.LevelData == null) { return; } + + level.LevelData.EventsExhausted = true; + if (level.LevelData.Type == LevelData.LevelType.Outpost) { - level.LevelData.EventsExhausted = true; - if (level.LevelData.Type == LevelData.LevelType.Outpost) + level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e))); + if (level.LevelData.EventHistory.Count > MaxEventHistory) { - level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e))); - if (level.LevelData.EventHistory.Count > MaxEventHistory) - { - level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory); - } - level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e))); - } - foreach (var usedUniqueSet in usedUniqueSets) - { - if (!level.LevelData.UsedUniqueSets.Contains(usedUniqueSet.Identifier)) - { - level.LevelData.UsedUniqueSets.Add(usedUniqueSet.Identifier); - } + level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory); } } + level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e))); } public void SkipEventCooldown() @@ -418,16 +408,11 @@ namespace Barotrauma DebugConsole.NewMessage($"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly: true); - if (eventSet.Unique && !usedUniqueSets.Contains(eventSet)) - { - usedUniqueSets.Add(eventSet); - } - int applyCount = 1; List> spawnPosFilter = new List>(); if (eventSet.PerRuin) { - applyCount = level.Ruins.Count(); + applyCount = level.Ruins.Count; foreach (var ruin in level.Ruins) { spawnPosFilter.Add(pos => pos.Ruin == ruin); @@ -435,7 +420,7 @@ namespace Barotrauma } else if (eventSet.PerCave) { - applyCount = level.Caves.Count(); + applyCount = level.Caves.Count; foreach (var cave in level.Caves) { spawnPosFilter.Add(pos => pos.Cave == cave); @@ -452,8 +437,8 @@ namespace Barotrauma } bool isPrefabSuitable(EventPrefab e) - => e.BiomeIdentifier.IsEmpty || - e.BiomeIdentifier == level.LevelData?.Biome?.Identifier; + => (e.BiomeIdentifier.IsEmpty || e.BiomeIdentifier == level.LevelData?.Biome?.Identifier) && + !level.LevelData.NonRepeatableEvents.Contains(e); foreach (var subEventPrefab in eventSet.EventPrefabs) { @@ -610,8 +595,7 @@ namespace Barotrauma return level.Difficulty >= eventSet.MinLevelDifficulty && level.Difficulty <= eventSet.MaxLevelDifficulty && level.LevelData.Type == eventSet.LevelType && - (eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier) && - (!eventSet.Unique || !level.LevelData.UsedUniqueSets.Contains(eventSet.Identifier)); + (eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier); } private bool IsValidForLocation(EventSet eventSet, Location location) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs index bb8bbd1aa..a5f16c350 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs @@ -114,15 +114,9 @@ namespace Barotrauma public readonly bool DisableInHuntingGrounds; /// - /// If true, events from this set shouldn't be selected again as long as they remain in which has a limited size. - /// Use to prevent selecting the whole set again altogether. + /// If true, events from this set can only occur once in the level. /// - public readonly bool OncePerOutpost; - - /// - /// If true, the whole set can only be selected once for a level. - /// - public readonly bool Unique; + public readonly bool OncePerLevel; public readonly bool DelayWhenCrewAway; @@ -289,8 +283,7 @@ namespace Barotrauma DisableInHuntingGrounds = element.GetAttributeBool("disableinhuntinggrounds", false); IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (PerRuin || PerCave || PerWreck)); DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck); - OncePerOutpost = element.GetAttributeBool("onceperoutpost", false); - Unique = element.GetAttributeBool("unique", false); + OncePerLevel = element.GetAttributeBool("onceperlevel", element.GetAttributeBool("onceperoutpost", false)); TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true); IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false)); ResetTime = element.GetAttributeFloat("resettime", 0); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index e826d0897..5c43dd00b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -386,23 +386,27 @@ namespace Barotrauma #if CLIENT foreach (Character character in crewCharacters) { - var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f); - character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual); - character.Info?.GiveExperience(experienceGain, isMissionExperience: true); + GiveMissionExperience(character.Info); } #else foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients) { //give the experience to the stored characterinfo if the client isn't currently controlling a character - CharacterInfo info = c.Character?.Info ?? c.CharacterInfo; - - var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f); - info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual); - - info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value), isMissionExperience: true); + GiveMissionExperience(c.Character?.Info ?? c.CharacterInfo); + } + foreach (Character bot in GameSession.GetSessionCrewCharacters(CharacterType.Bot)) + { + GiveMissionExperience(bot.Info); } #endif + void GiveMissionExperience(CharacterInfo info) + { + var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f); + info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual); + info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value), isMissionExperience: true); + } + // apply money gains afterwards to prevent them from affecting XP gains var missionMoneyGainMultiplier = new AbilityMissionMoneyGainMultiplier(this, 1f); crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, missionMoneyGainMultiplier)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index 4347b8687..adb68793d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -306,14 +306,6 @@ namespace Barotrauma return validContainers; } - private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1] - { - (0, 1.0f), - (1, 0.0f), - (2, 0.0f), - (3, 0.0f), - }; - private static List CreateItems(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer) { List newItems = new List(); @@ -336,11 +328,7 @@ namespace Barotrauma break; } var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.Prefab == itemPrefab); - int quality = - existingItem?.Quality ?? - ToolBox.SelectWeightedRandom( - qualityCommonnesses.Select(q => q.quality).ToList(), - qualityCommonnesses.Select(q => q.commonness).ToList(), Rand.RandSync.ServerAndClient); + int quality = existingItem?.Quality ?? Quality.GetSpawnedItemQuality(validContainer.Key.Item.Submarine, Level.Loaded, Rand.RandSync.ServerAndClient); if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; } var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine, callOnItemLoaded: false) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs index c6b403f9e..64db689bd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs @@ -276,7 +276,7 @@ namespace Barotrauma.Items.Components } } - //get all walls within range + //get all walls within range the arc could potentially hit List entitiesInRange = new List(100); foreach (Structure structure in Structure.WallList) { @@ -284,10 +284,10 @@ namespace Barotrauma.Items.Components if (structure.Submarine != null&& !submarinesInRange.Contains(structure.Submarine)) { continue; } var structureWorldRect = structure.WorldRect; - if (worldPosition.X < structureWorldRect.X - range) continue; - if (worldPosition.X > structureWorldRect.Right + range) continue; - if (worldPosition.Y > structureWorldRect.Y + range) continue; - if (worldPosition.Y < structureWorldRect.Y -structureWorldRect.Height - range) continue; + if (worldPosition.X < structureWorldRect.X - range) { continue; } + if (worldPosition.X > structureWorldRect.Right + range) { continue; } + if (worldPosition.Y > structureWorldRect.Y + range) { continue; } + if (worldPosition.Y < structureWorldRect.Y - structureWorldRect.Height - range) { continue; } if (structure.Submarine != null) { @@ -317,6 +317,7 @@ namespace Barotrauma.Items.Components nodes.Add(new Node(worldPosition, -1)); } + //get all characters within range the arc could potentially hit float totalRange = RaycastRange + range; foreach (Character character in Character.CharacterList) { @@ -325,11 +326,20 @@ namespace Barotrauma.Items.Components if (OutdoorsOnly && character.Submarine != null) { continue; } if (character.Submarine != null && !submarinesInRange.Contains(character.Submarine)) { continue; } - if (Vector2.DistanceSquared(character.WorldPosition, worldPosition) < totalRange * totalRange * RangeMultiplierInWalls || - (RaycastRange > 0.0f && MathUtils.LineToPointDistanceSquared(worldPosition, item.WorldPosition, character.WorldPosition) < range * range * RangeMultiplierInWalls)) + if (Vector2.DistanceSquared(character.WorldPosition, worldPosition) < totalRange * totalRange * RangeMultiplierInWalls) { entitiesInRange.Add(character); - charactersInRange.Add((character, nodes[0])); + } + //if the weapon does a raycast, check distance to the ray too (not just the end of the ray) + if (RaycastRange > 0) + { + float distSqr = MathUtils.LineSegmentToPointDistanceSquared(worldPosition, item.WorldPosition, character.WorldPosition); + //if the distance from the initial raycast to the character is small (e.g. goes through the character), we know it must hit + if (distSqr < range * range * RangeMultiplierInWalls) + { + if (!entitiesInRange.Contains(character)) { entitiesInRange.Add(character); } + charactersInRange.Add((character, nodes.First())); + } } } @@ -378,7 +388,7 @@ namespace Barotrauma.Items.Components } else if (entitiesInRange[i] is Character character) { - dist = Vector2.Distance(character.WorldPosition, currPos); + dist = MathUtils.LineSegmentToPointDistanceSquared(currPos, nodes[parentNodeIndex].WorldPosition, character.WorldPosition); } if (dist < closestDist) @@ -494,17 +504,31 @@ namespace Barotrauma.Items.Components if (IgnoreUser && character == user) { continue; } if (OutdoorsOnly && character.Submarine != null) { continue; } + Vector2 characterMin = new Vector2(character.AnimController.Limbs.Min(l => l.WorldPosition.X), character.AnimController.Limbs.Min(l => l.WorldPosition.Y)); + Vector2 characterMax = new Vector2(character.AnimController.Limbs.Max(l => l.WorldPosition.X), character.AnimController.Limbs.Max(l => l.WorldPosition.Y)); if (targetStructure.IsHorizontal) { - if (otherEntity.WorldPosition.X < targetStructure.WorldRect.X) { continue; } - if (otherEntity.WorldPosition.X > targetStructure.WorldRect.Right) { continue; } - if (Math.Abs(otherEntity.WorldPosition.Y - targetStructure.WorldPosition.Y) > currentRange) { continue; } + if (characterMax.X < targetStructure.WorldRect.X) { continue; } + if (characterMin.X > targetStructure.WorldRect.Right) { continue; } + if (Math.Abs(characterMin.Y - targetStructure.WorldPosition.Y) > currentRange && + Math.Abs(characterMax.Y - targetStructure.WorldPosition.Y) > currentRange) + { + continue; + } } else { - if (otherEntity.WorldPosition.Y < targetStructure.WorldRect.Y - targetStructure.Rect.Height) { continue; } - if (otherEntity.WorldPosition.Y > targetStructure.WorldRect.Y) { continue; } - if (Math.Abs(otherEntity.WorldPosition.X - targetStructure.WorldPosition.X) > currentRange) { continue; } + if (characterMax.Y < targetStructure.WorldRect.Y - targetStructure.Rect.Height) { continue; } + if (characterMin.Y > targetStructure.WorldRect.Y) { continue; } + if (Math.Abs(characterMin.X - targetStructure.WorldPosition.X) > currentRange && + Math.Abs(characterMax.X - targetStructure.WorldPosition.X) > currentRange) + { + continue; + } + } + if (!charactersInRange.Any(c => c.character == character)) + { + charactersInRange.Add((character, nodes[parentNodeIndex])); } float closestNodeDistSqr = float.MaxValue; int closestNodeIndex = -1; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs index fa2cf6dbd..0c45b1548 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs @@ -12,7 +12,7 @@ namespace Barotrauma.Items.Components { public enum UseEnvironment { - Air, Water, Both + Air, Water, Both, None }; private float useState; @@ -44,6 +44,7 @@ namespace Barotrauma.Items.Components { if (character == null || character.Removed) { return false; } if (!character.IsKeyDown(InputType.Aim) || character.Stun > 0.0f) { return false; } + if (UsableIn == UseEnvironment.None) { return false; } IsActive = true; useState = 0.1f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index fac2fc0f0..e7b7cb691 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -476,6 +476,7 @@ namespace Barotrauma.Items.Components { foreach (Item item in Inventory.AllItemsMod) { + item.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, ownerCharacter); item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter); item.GetComponent()?.Equip(ownerCharacter); autoInjectCooldown = AutoInjectInterval; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index e11973068..0499ce20d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -450,6 +450,7 @@ namespace Barotrauma.Items.Components public override bool Select(Character activator) { if (activator == null || activator.Removed) { return false; } + if (Item.Condition <= 0.0f && !UpdateWhenInactive) { return false; } if (UsableIn == UseEnvironment.Water && !activator.AnimController.InWater || UsableIn == UseEnvironment.Air && activator.AnimController.InWater) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index e2c8cd0b0..4a4775208 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -15,6 +15,8 @@ namespace Barotrauma.Items.Components { private ImmutableDictionary fabricationRecipes; //this is not readonly because tutorials fuck this up!!!! + private const int MaxAmountToFabricate = 99; + private FabricationRecipe fabricatedItem; private float timeUntilReady; private float requiredTime; @@ -39,6 +41,16 @@ namespace Barotrauma.Items.Components [Serialize(1.0f, IsPropertySaveable.Yes)] public float SkillRequirementMultiplier { get; set; } + private int amountToFabricate; + [Serialize(1, IsPropertySaveable.Yes)] + public int AmountToFabricate + { + get { return amountToFabricate; } + set { amountToFabricate = MathHelper.Clamp(value, 1, MaxAmountToFabricate); } + } + + private int amountRemaining; + private const float TinkeringSpeedIncrease = 2.5f; private enum FabricatorState @@ -183,16 +195,20 @@ namespace Barotrauma.Items.Components if (selectedItem == null) { return; } if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health)) { return; } -#if CLIENT - itemList.Enabled = false; - activateButton.Text = TextManager.Get("FabricatorCancel"); -#endif - IsActive = true; this.user = user; fabricatedItem = selectedItem; RefreshAvailableIngredients(); +#if CLIENT + itemList.Enabled = false; + if (amountInput != null) + { + amountInput.Enabled = false; + } + RefreshActivateButtonText(); +#endif + bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient; if (!isClient) { @@ -249,10 +265,11 @@ namespace Barotrauma.Items.Components } #elif CLIENT itemList.Enabled = true; - if (activateButton != null) + if (amountInput != null) { - activateButton.Text = TextManager.Get(CreateButtonText); + amountInput.Enabled = true; } + RefreshActivateButtonText(); #endif fabricatedItem = null; } @@ -518,20 +535,16 @@ namespace Barotrauma.Items.Components } } - //disabled "continuous fabrication" for now - //before we enable it, there should be some UI controls for fabricating a specific number of items - - /*var prevFabricatedItem = fabricatedItem; + var prevFabricatedItem = fabricatedItem; var prevUser = user; CancelFabricating(); - if (CanBeFabricated(prevFabricatedItem)) + + amountRemaining--; + if (amountRemaining > 0 && CanBeFabricated(prevFabricatedItem, availableIngredients, prevUser)) { //keep fabricating if we can fabricate more StartFabricating(prevFabricatedItem, prevUser, addToServerLog: false); - }*/ - - - CancelFabricating(); + } } } @@ -594,7 +607,15 @@ namespace Barotrauma.Items.Components private bool CanBeFabricated(FabricationRecipe fabricableItem, IReadOnlyDictionary> availableIngredients, Character character) { if (fabricableItem == null) { return false; } - if (fabricableItem.RequiresRecipe && (character == null || !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier))) { return false; } + if (fabricableItem.RequiresRecipe) + { + if (character == null) { return false; } + if (!character.HasRecipeForItem(fabricableItem.TargetItem.Identifier) && + GameSession.GetSessionCrewCharacters(CharacterType.Bot).None(c => c.HasRecipeForItem(fabricableItem.TargetItem.Identifier))) + { + return false; + } + } if (fabricableItem.RequiredMoney > 0) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs index 250c5b37a..807c13d23 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs @@ -151,7 +151,6 @@ namespace Barotrauma.Items.Components set { bool changed = currentMode != value; - currentMode = value; #if CLIENT if (changed) { prevPassivePingRadius = float.MaxValue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs index fbdb9e7c4..75426b030 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -1,6 +1,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components @@ -9,14 +10,6 @@ namespace Barotrauma.Items.Components { public const int MaxQuality = 3; - public static readonly float[] QualityCommonnesses = new float[] - { - 0.8f, - 0.15f, - 0.045f, - 0.005f, - }; - public enum StatType { Condition, @@ -81,5 +74,29 @@ namespace Barotrauma.Items.Components if (!statValues.ContainsKey(statType)) { return 0.0f; } return statValues[statType] * qualityLevel; } + + /// + /// Get a random quality for an item spawning in some sub, taking into account the type of the submarine and the difficulty of the current level + /// (high-quality items become more common as difficulty increases) + /// + public static int GetSpawnedItemQuality(Submarine submarine, Level level, Rand.RandSync randSync = Rand.RandSync.ServerAndClient) + { + if (submarine?.Info == null || level == null || submarine.Info.Type == SubmarineType.Player) { return 0; } + + float difficultyFactor = MathHelper.Clamp(level.Difficulty, 0.0f, 1.0f); + return ToolBox.SelectWeightedRandom(Enumerable.Range(0, MaxQuality + 1), q => GetCommonness(q, difficultyFactor), randSync); + + static float GetCommonness(int quality, float difficultyFactor) + { + return quality switch + { + 0 => 1, + 1 => MathHelper.Lerp(0.0f, 1f, difficultyFactor), + 2 => MathHelper.Lerp(0.0f, 1f, Math.Max(difficultyFactor-0.15f, 0f)), //15 difficulty transition to next biome - unlock Excellent loot + 3 => MathHelper.Lerp(0.0f, 1f, Math.Max(difficultyFactor-0.35f, 0f)), //35 difficulty transition to next biome - unlock Masterwork loot + _ => 0.0f, + }; + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index bed7cc150..dcf4b69bc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1617,11 +1617,7 @@ namespace Barotrauma public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, bool checkCondition = true, Vector2? worldPosition = null) { - if (effect.intervalTimer > 0.0f) - { - effect.intervalTimer -= deltaTime; - return; - } + if (effect.ShouldWaitForInterval(this, deltaTime)) { return; } if (!isNetworkEvent && checkCondition) { if (condition == 0.0f && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { return; } @@ -2758,7 +2754,6 @@ namespace Barotrauma return; } #endif - bool remove = false; foreach (ItemComponent ic in components) { @@ -2788,7 +2783,6 @@ namespace Barotrauma { var abilityItem = new AbilityApplyTreatment(user, character, this); user.CheckTalents(AbilityEffectType.OnApplyTreatment, abilityItem); - } if (remove) { Spawner?.AddItemToRemoveQueue(this); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemStatManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemStatManager.cs index 4ad18a238..c6091915f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemStatManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemStatManager.cs @@ -15,10 +15,7 @@ namespace Barotrauma } [NetworkSerialize] - public readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, UInt32 CharacterID) : INetSerializableStruct - { - public override int GetHashCode() => HashCode.Combine(TalentIdentifier, CharacterID, Stat); - } + public readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, UInt32 CharacterID) : INetSerializableStruct; private readonly Dictionary talentStats = new(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index a0edc5b26..241138e99 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -3943,31 +3943,11 @@ namespace Barotrauma Submarine outpost = null; if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null) { - if (OutpostGenerationParams.OutpostParams.Any() || LevelData.ForceOutpostGenerationParams != null) + if (LevelData.OutpostGenerationParamsExist) { Location location = i == 0 ? StartLocation : EndLocation; - - OutpostGenerationParams outpostGenerationParams = null; - if (LevelData.ForceOutpostGenerationParams != null) - { - outpostGenerationParams = LevelData.ForceOutpostGenerationParams; - } - else - { - var suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier)); - if (!suitableParams.Any()) - { - suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || !p.AllowedLocationTypes.Any()); - if (!suitableParams.Any()) - { - DebugConsole.ThrowError($"No suitable outpost generation parameters found for the location type \"{location.Type.Identifier}\". Selecting random parameters."); - suitableParams = OutpostGenerationParams.OutpostParams; - } - } - - outpostGenerationParams = suitableParams.GetRandom(Rand.RandSync.ServerAndClient); - } - + OutpostGenerationParams outpostGenerationParams = LevelData.ForceOutpostGenerationParams ?? + LevelData.GetSuitableOutpostGenerationParams(location).GetRandom(Rand.RandSync.ServerAndClient); LocationType locationType = location?.Type; if (locationType == null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs index 8e9ca73d1..1ac0bfd73 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs @@ -57,10 +57,19 @@ namespace Barotrauma /// public int? MinMainPathWidth; + /// + /// Events that have previously triggered in this level. Used for making events the player hasn't seen yet more likely to trigger when re-entering the level. Has a maximum size of . + /// public readonly List EventHistory = new List(); - public readonly List NonRepeatableEvents = new List(); - public readonly HashSet UsedUniqueSets = new HashSet(); + /// + /// Events that have already triggered in this level and can never trigger again. . + /// + public readonly List NonRepeatableEvents = new List(); + + /// + /// 'Exhaustible' sets won't appear in the same level until after one world step (~10 min, see Map.ProgressWorld) has passed. . + /// public bool EventsExhausted { get; set; } /// @@ -144,8 +153,6 @@ namespace Barotrauma string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", new string[] { }); NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n))); - UsedUniqueSets = element.GetAttributeIdentifierArray(nameof(UsedUniqueSets), Array.Empty()).ToHashSet(); - EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false); } @@ -245,6 +252,23 @@ namespace Barotrauma return levelData; } + public bool OutpostGenerationParamsExist => ForceOutpostGenerationParams != null || OutpostGenerationParams.OutpostParams.Any(); + + public static IEnumerable GetSuitableOutpostGenerationParams(Location location) + { + var suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier)); + if (!suitableParams.Any()) + { + suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || !p.AllowedLocationTypes.Any()); + if (!suitableParams.Any()) + { + DebugConsole.ThrowError($"No suitable outpost generation parameters found for the location type \"{location.Type.Identifier}\". Selecting random parameters."); + suitableParams = OutpostGenerationParams.OutpostParams; + } + } + return suitableParams; + } + public void Save(XElement parentElement) { var newElement = new XElement("Level", @@ -287,11 +311,6 @@ namespace Barotrauma } } - if (UsedUniqueSets.Any()) - { - newElement.Add(new XAttribute(nameof(UsedUniqueSets), string.Join(',', UsedUniqueSets))); - } - parentElement.Add(newElement); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index a8426ed70..0f9a8782d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -1291,19 +1291,32 @@ namespace Barotrauma return characters.Sum(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount)); } - public int HighestSubmarineTierAvailable(SubmarineClass submarineClass) + public bool CanHaveSubsForSale() { - if (!HasOutpost()) { return 0; } - return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier; + return HasOutpost() && CanHaveCampaignInteraction(CampaignMode.InteractionType.PurchaseSub); } - public int HighestSubmarineTierAvailable() => HighestSubmarineTierAvailable(SubmarineClass.Undefined); + public int HighestSubmarineTierAvailable(SubmarineClass submarineClass = SubmarineClass.Undefined) + { + if (CanHaveSubsForSale()) + { + return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier; + } + return 0; + } public bool IsSubmarineAvailable(SubmarineInfo info) { return Biome?.IsSubmarineAvailable(info, Type.Identifier) ?? true; } + private bool CanHaveCampaignInteraction(CampaignMode.InteractionType interactionType) + { + return LevelData != null && + LevelData.OutpostGenerationParamsExist && + LevelData.GetSuitableOutpostGenerationParams(this).Any(p => p.CanHaveCampaignInteraction(interactionType)); + } + public void Reset() { if (Type != OriginalType) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs index fdb6aad08..2a97aa299 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs @@ -85,9 +85,9 @@ namespace Barotrauma } public float StoreMaxReputationModifier { get; } = 0.1f; - public float StoreSellPriceModifier { get; } = 0.8f; + public float StoreSellPriceModifier { get; } = 0.3f; public float DailySpecialPriceModifier { get; } = 0.5f; - public float RequestGoodPriceModifier { get; } = 1.5f; + public float RequestGoodPriceModifier { get; } = 2f; public int StoreInitialBalance { get; } = 5000; /// /// In percentages diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs index aa8082144..f91f5e1a6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs @@ -260,6 +260,21 @@ namespace Barotrauma return humanPrefabCollections.GetRandom(randSync); } + public bool CanHaveCampaignInteraction(CampaignMode.InteractionType interactionType) + { + foreach (var collection in humanPrefabCollections) + { + foreach (var prefab in collection) + { + if (prefab.CampaignInteractionType == interactionType) + { + return true; + } + } + } + return false; + } + public ImmutableHashSet GetStoreIdentifiers() { if (StoreIdentifiers == null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs index 64e8c5517..704d3f9db 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs @@ -164,7 +164,11 @@ namespace Barotrauma.Networking return command; } - public static float GetGarbleAmount(Entity listener, Entity sender, float range, float obstructionmult = 2.0f) + /// + /// How much messages sent by should get garbled. Takes the distance between the entities and optionally the obstructions between them into account (see ). + /// + /// Values greater than or equal to 1 cause the message to get garbled more heavily when there's some obstruction between the characters. Values smaller than 1 mean the garbling only depends on distance. + public static float GetGarbleAmount(Entity listener, Entity sender, float range, float obstructionMultiplier = 2.0f) { if (listener == null || sender == null) { @@ -177,12 +181,12 @@ namespace Barotrauma.Networking Hull listenerHull = listener == null ? null : Hull.FindHull(listener.WorldPosition); Hull sourceHull = sender == null ? null : Hull.FindHull(sender.WorldPosition); - if (sourceHull != listenerHull) + if (sourceHull != listenerHull && obstructionMultiplier >= 1.0f) { if ((sourceHull == null || !sourceHull.GetConnectedHulls(includingThis: false, searchDepth: 2, ignoreClosedGaps: true).Contains(listenerHull)) && Submarine.CheckVisibility(listener.SimPosition, sender.SimPosition) != null) { - dist = (dist + 100f) * obstructionmult; + dist = (dist + 100f) * obstructionMultiplier; } } if (dist > range) { return 1.0f; } @@ -197,9 +201,9 @@ namespace Barotrauma.Networking return ApplyDistanceEffect(listener, Sender, Text, SpeakRange); } - public static string ApplyDistanceEffect(Entity listener, Entity sender, string text, float range, float obstructionmult = 2.0f) + public static string ApplyDistanceEffect(Entity listener, Entity sender, string text, float range, float obstructionMultiplier = 2.0f) { - return ApplyDistanceEffect(text, GetGarbleAmount(listener, sender, range, obstructionmult)); + return ApplyDistanceEffect(text, GetGarbleAmount(listener, sender, range, obstructionMultiplier)); } public static string ApplyDistanceEffect(string text, float garbleAmount) @@ -252,7 +256,7 @@ namespace Barotrauma.Networking var senderRadio = senderItem.GetComponent(); if (!receiverRadio.CanReceive(senderRadio)) { continue; } - string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range); + string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range, obstructionMultiplier: 0); if (sender.SpeechImpediment > 0.0f) { //speech impediment doesn't reduce the range when using a radio, but adds extra garbling diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Address/LidgrenAddress.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Address/LidgrenAddress.cs index eaea9b556..bb8eeaa40 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Address/LidgrenAddress.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Address/LidgrenAddress.cs @@ -17,14 +17,9 @@ namespace Barotrauma.Networking public LidgrenAddress(IPAddress netAddress) { - if (IPAddress.IsLoopback(netAddress)) - { - NetAddress = IPAddress.Loopback; - } - else - { - NetAddress = netAddress; - } + if (IPAddress.IsLoopback(netAddress)) { netAddress = IPAddress.Loopback; } + if (netAddress.IsIPv4MappedToIPv6) { netAddress = netAddress.MapToIPv4(); } + NetAddress = netAddress; } public new static Option Parse(string endpointStr) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Endpoint/LidgrenEndpoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Endpoint/LidgrenEndpoint.cs index 8e11264c6..6fc7a7c8d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Endpoint/LidgrenEndpoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Endpoint/LidgrenEndpoint.cs @@ -19,7 +19,7 @@ namespace Barotrauma.Networking public LidgrenEndpoint(IPEndPoint netEndpoint) : base(new LidgrenAddress(netEndpoint.Address)) { - NetEndpoint = netEndpoint; + NetEndpoint = new IPEndPoint((Address as LidgrenAddress)!.NetAddress, netEndpoint.Port); } public new static Option Parse(string endpointStr) diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs index 6435eebf4..60b0aca6d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs @@ -95,11 +95,7 @@ namespace Barotrauma public override void Apply(ActionType type, float deltaTime, Entity entity, IReadOnlyList targets, Vector2? worldPosition = null) { if (this.type != type) { return; } - if (intervalTimer > 0.0f) - { - intervalTimer -= deltaTime; - return; - } + if (ShouldWaitForInterval(entity, deltaTime)) { return; } if (!HasRequiredItems(entity)) { return; } if (delayType == DelayTypes.ReachCursor && Character.Controlled == null) { return; } if (!Stackable) diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs index b831c7670..301137e0a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs @@ -416,22 +416,26 @@ namespace Barotrauma return false; } - switch (Operator) { case OperatorType.Equals: - if (type == typeof(bool)) { - return property.GetBoolValue(target) == (AttributeValue == "true" || AttributeValue == "True"); + if (type == typeof(bool)) + { + return property.GetBoolValue(target) == (AttributeValue == "true" || AttributeValue == "True"); + } + var value = property.GetValue(target); + return Equals(value, AttributeValue); } - return property.GetValue(target).ToString().Equals(AttributeValue); - case OperatorType.NotEquals: - if (type == typeof(bool)) { - return property.GetBoolValue(target) != (AttributeValue == "true" || AttributeValue == "True"); + if (type == typeof(bool)) + { + return property.GetBoolValue(target) != (AttributeValue == "true" || AttributeValue == "True"); + } + var value = property.GetValue(target); + return !Equals(value, AttributeValue); } - return !property.GetValue(target).ToString().Equals(AttributeValue); case OperatorType.GreaterThan: case OperatorType.LessThanEquals: case OperatorType.LessThan: @@ -441,6 +445,18 @@ namespace Barotrauma break; } return false; + + static bool Equals(object value, string desiredValue) + { + if (value == null) + { + return desiredValue.Equals("null", StringComparison.OrdinalIgnoreCase); + } + else + { + return value.ToString().Equals(desiredValue); + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 581a5ccc5..f471e7be9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -343,7 +343,7 @@ namespace Barotrauma private readonly float lifeTime; private float lifeTimer; - public float intervalTimer; + public Dictionary intervalTimers = new Dictionary(); public static readonly List DurationList = new List(); @@ -1120,6 +1120,26 @@ namespace Barotrauma } } + private static readonly List intervalsToRemove = new List(); + public bool ShouldWaitForInterval(Entity entity, float deltaTime) + { + if (Interval > 0.0f && entity != null) + { + if (intervalTimers.ContainsKey(entity)) + { + intervalTimers[entity] -= deltaTime; + if (intervalTimers[entity] > 0.0f) { return true; } + } + intervalsToRemove.Clear(); + intervalsToRemove.AddRange(intervalTimers.Keys.Where(e => e.Removed)); + foreach (var toRemove in intervalsToRemove) + { + intervalTimers.Remove(toRemove); + } + } + return false; + } + public virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition = null) { if (this.type != type || !HasRequiredItems(entity)) { return; } @@ -1147,12 +1167,7 @@ namespace Barotrauma public virtual void Apply(ActionType type, float deltaTime, Entity entity, IReadOnlyList targets, Vector2? worldPosition = null) { if (this.type != type) { return; } - - if (intervalTimer > 0.0f) - { - intervalTimer -= deltaTime; - return; - } + if (ShouldWaitForInterval(entity, deltaTime)) { return; } currentTargets.Clear(); foreach (ISerializableEntity target in targets) @@ -1255,11 +1270,8 @@ namespace Barotrauma lifeTimer -= deltaTime; if (lifeTimer <= 0) { return; } } - if (intervalTimer > 0.0f) - { - intervalTimer -= deltaTime; - return; - } + if (ShouldWaitForInterval(entity, deltaTime)) { return; } + Hull hull = GetHull(entity); Vector2 position = GetPosition(entity, targets, worldPosition); if (useItemCount > 0) @@ -1942,7 +1954,10 @@ namespace Barotrauma ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound: true); - intervalTimer = Interval; + if (Interval > 0.0f && entity != null) + { + intervalTimers[entity] = Interval; + } static Character CharacterFromTarget(ISerializableEntity target) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Text/TextManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Text/TextManager.cs index f4bd75f05..53f0d75cf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Text/TextManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Text/TextManager.cs @@ -1,12 +1,12 @@ #nullable enable using Barotrauma.IO; +using Barotrauma.Extensions; using System; using System.Collections.Generic; using System.Collections.Concurrent; using System.Collections.Immutable; using System.Linq; -using System.Text.RegularExpressions; using System.Xml.Linq; using System.Globalization; using System.Text.Unicode; @@ -33,54 +33,104 @@ namespace Barotrauma public static int LanguageVersion { get; private set; } = 0; - private static readonly ImmutableArray> CjkRanges = new[] - { - UnicodeRanges.HangulJamo, - UnicodeRanges.Hiragana, - UnicodeRanges.Katakana, - UnicodeRanges.CjkRadicalsSupplement, - UnicodeRanges.CjkSymbolsandPunctuation, - UnicodeRanges.EnclosedCjkLettersandMonths, - UnicodeRanges.CjkCompatibility, - UnicodeRanges.CjkUnifiedIdeographsExtensionA, - UnicodeRanges.CjkUnifiedIdeographs, - UnicodeRanges.HangulSyllables, - UnicodeRanges.CjkCompatibilityForms - }.Select(r => new Range(r.FirstCodePoint, r.FirstCodePoint+r.Length-1)) - .OrderBy(r => r.Start) - .ToImmutableArray(); + private static ImmutableArray> UnicodeToIntRanges(params UnicodeRange[] ranges) + => ranges + .Select(r => new Range(r.FirstCodePoint, r.FirstCodePoint + r.Length - 1)) + .OrderBy(r => r.Start) + .ToImmutableArray(); - /// - /// Does the string contain symbols from Chinese, Japanese or Korean languages - /// - public static bool IsCJK(LocalizedString text) + [Flags] + public enum SpeciallyHandledCharCategory { - return IsCJK(text.Value); + None = 0x0, + + CJK = 0x1, + Cyrillic = 0x2, + + All = 0x3 } - public static bool IsCJK(string text) - { - if (string.IsNullOrEmpty(text)) { return false; } + public static readonly ImmutableArray SpeciallyHandledCharCategories + = Enum.GetValues() + .Where(c => c is not (SpeciallyHandledCharCategory.None or SpeciallyHandledCharCategory.All)) + .ToImmutableArray(); + + private static readonly ImmutableDictionary>> SpeciallyHandledCharacterRanges + = new[] + { + (SpeciallyHandledCharCategory.CJK, UnicodeToIntRanges( + UnicodeRanges.HangulJamo, + UnicodeRanges.Hiragana, + UnicodeRanges.Katakana, + UnicodeRanges.CjkRadicalsSupplement, + UnicodeRanges.CjkSymbolsandPunctuation, + UnicodeRanges.EnclosedCjkLettersandMonths, + UnicodeRanges.CjkCompatibility, + UnicodeRanges.CjkUnifiedIdeographsExtensionA, + UnicodeRanges.CjkUnifiedIdeographs, + UnicodeRanges.HangulSyllables, + UnicodeRanges.CjkCompatibilityForms + )), + (SpeciallyHandledCharCategory.Cyrillic, UnicodeToIntRanges( + UnicodeRanges.Cyrillic, + UnicodeRanges.CyrillicSupplement, + UnicodeRanges.CyrillicExtendedA, + UnicodeRanges.CyrillicExtendedB, + UnicodeRanges.CyrillicExtendedC + )) + }.ToImmutableDictionary(); + public static SpeciallyHandledCharCategory GetSpeciallyHandledCategories(LocalizedString text) + => GetSpeciallyHandledCategories(text.Value); + + public static SpeciallyHandledCharCategory GetSpeciallyHandledCategories(string text) + { + if (string.IsNullOrEmpty(text)) { return SpeciallyHandledCharCategory.None; } + + var retVal = SpeciallyHandledCharCategory.None; for (int i = 0; i < text.Length; i++) { char chr = text[i]; - for (int j = 0; j < CjkRanges.Length; j++) + + foreach (var category in SpeciallyHandledCharCategories) { - var range = CjkRanges[j]; + if (retVal.HasFlag(category)) { continue; } + + for (int j = 0; j < SpeciallyHandledCharacterRanges[category].Length; j++) + { + var range = SpeciallyHandledCharacterRanges[category][j]; - // If chr < range.Start, we know that it can't - // be in any of the following ranges, so let's - // not even bother checking them - if (chr < range.Start) { break; } + // If chr < range.Start, we know that it can't + // be in any of the following ranges, so let's + // not even bother checking them + if (chr < range.Start) { break; } - // This character is in a range, return true - if (range.Contains(chr)) { return true; } + // This character is in a range, set the flag + if (range.Contains(chr)) + { + retVal |= category; + break; + } + } + } + + if (retVal == SpeciallyHandledCharCategory.All) + { + // Input contains characters from all + // specially handled categories, there's + // no need to inspect the string further + return SpeciallyHandledCharCategory.All; } } - return false; + return retVal; } + public static bool IsCJK(LocalizedString text) + => IsCJK(text.Value); + + public static bool IsCJK(string text) + => GetSpeciallyHandledCategories(text).HasFlag(SpeciallyHandledCharCategory.CJK); + /// /// Check if the currently selected language is available, and switch to English if not /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs index 0d6974172..872ba4426 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs @@ -562,35 +562,45 @@ namespace Barotrauma public static double LineSegmentToPointDistanceSquared(Point lineA, Point lineB, Point point) { - double xDiff = lineB.X - lineA.X; - double yDiff = lineB.Y - lineA.Y; + return LineSegmentToPointDistanceSquared(lineA.X, lineA.Y, lineB.X, lineB.Y, point.X, point.Y); + } + + public static float LineSegmentToPointDistanceSquared(Vector2 lineA, Vector2 lineB, Vector2 point) + { + return (float)LineSegmentToPointDistanceSquared(lineA.X, lineA.Y, lineB.X, lineB.Y, point.X, point.Y); + } + + private static double LineSegmentToPointDistanceSquared(double line1X, double line1Y, double line2X, double line2Y, double pointX, double pointY) + { + double xDiff = line2X - line1X; + double yDiff = line2Y - line1Y; if (xDiff == 0 && yDiff == 0) { - double v1 = lineA.X - point.X; - double v2 = lineA.Y - point.Y; + double v1 = line1X - pointX; + double v2 = line1Y - pointY; return (v1 * v1) + (v2 * v2); } // Calculate the t that minimizes the distance. - double t = ((point.X - lineA.X) * xDiff + (point.Y - lineA.Y) * yDiff) / (xDiff * xDiff + yDiff * yDiff); + double t = ((pointX - line1X) * xDiff + (pointY - line1Y) * yDiff) / (xDiff * xDiff + yDiff * yDiff); // See if this represents one of the segment's // end points or a point in the middle. if (t < 0) { - xDiff = point.X - lineA.X; - yDiff = point.Y - lineA.Y; + xDiff = pointX - line1X; + yDiff = pointY - line1Y; } else if (t > 1) { - xDiff = point.X - lineB.X; - yDiff = point.Y - lineB.Y; + xDiff = pointX - line2X; + yDiff = pointY - line2Y; } else { - xDiff = point.X - (lineA.X + t * xDiff); - yDiff = point.Y - (lineA.Y + t * yDiff); + xDiff = pointX - (line1X + t * xDiff); + yDiff = pointY - (line1Y + t * yDiff); } return xDiff * xDiff + yDiff * yDiff; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs index dba2f7ea7..a7d065ed0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Barotrauma.IO; using System.Linq; +using System.Net; using System.Reflection; using System.Security.Cryptography; using System.Text; @@ -825,16 +826,29 @@ namespace Barotrauma public static bool StatIdentifierMatches(Identifier original, Identifier match) { if (original == match) { return true; } + return Matches(original, match) || Matches(match, original); - for (int i = 0; i < match.Value.Length; i++) + static bool Matches(Identifier a, Identifier b) { - if (i >= original.Value.Length) { return match[i] is '~'; } - if (!CharEquals(original[i], match[i])) { return false; } + for (int i = 0; i < b.Value.Length; i++) + { + if (i >= a.Value.Length) { return b[i] is '~'; } + if (!CharEquals(a[i], b[i])) { return false; } + } + return false; } - return false; - static bool CharEquals(char a, char b) => char.ToLowerInvariant(a) == char.ToLowerInvariant(b); } + + public static bool EquivalentTo(this IPEndPoint self, IPEndPoint other) + => self.Address.EquivalentTo(other.Address) && self.Port == other.Port; + + public static bool EquivalentTo(this IPAddress self, IPAddress other) + { + if (self.IsIPv4MappedToIPv6) { self = self.MapToIPv4(); } + if (other.IsIPv4MappedToIPv6) { other = other.MapToIPv4(); } + return self.Equals(other); + } } } diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index b137ab06e..3709783c7 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,47 @@ +--------------------------------------------------------------------------------------------------------- +v0.20.7.0 +--------------------------------------------------------------------------------------------------------- + +Changes: +- Added a slider to the fabricator that can be used to select how many items to fabricate. +- Chance of finding good/excellent/masterwork quality items in higher-difficulty levels. +- Changed engines' default repair threshold to 50 to 80. +- Quality of the radio voice chat diminishes with distance (gradually fading into radio static), similar to the way as the quality of the text chat. +- Voice chat range also affects spectators (= spectators can't hear players talking at the other side of the level). + +Unstable only: +- Changed how the restrictions on specialization talent trees work: instead of being only allowed to choose one, you can unlock all of them (but need to complete the entire specialization tree before you can choose another one). +- Any number of generic talents can be chosen (but you can still only pick talents from the next tier when you've unlocked 2 generic talents). +- Anyone can use the recipes bots have unlocked via talents. +- Various fixes, balance changes and improvements to talents. +- Fixed Arc Emitter damaging nearby characters that aren't near the arc (e.g. characters behind the shooter's back). +- Fixed null reference exception in EnemyAIController.UpdateLimbAttack (caused frequent crashes when getting attacked by swarms). +- Improvements to the exosuit sprite. +- Fixed bots not being able to apply medical treatment anymore. +- Fixed switching from a directional ping to passive immediately making the item's AITarget sector a full 360 degrees, and AITarget's sector not being taken into account when determining what it should reveal on passive sonar. +- Fixed submarine availability being displayed incorrectly on some locations (e.g. abandoned outposts) on the campaign map. +- Fixed feign death not working when you have a provocative items (e.g. items that make sounds or otherwise attrack monsters) in the inventory. +- Restored some of the wrecked sprites that were replaced with tinted version of the normal sprite in the previous builds (WIP). +- Fixed fiber plant sprite. +- Fixed bigbrother event getting stuck when choosing "not interested". +- Fixed autoinjectors (autoinjector headset, PUCS) no longer working. + +Bugfixes: +- Fixed main menu sometimes appearing half obstructed when starting the game on Mac. +- Fixed speech impediments only affecting the text chat, not the voice chat. +- Fixed radio voice chat not working properly if the range of the radio is larger than 250 meters. Happened because characters' positions aren't synced if they're further away than 250 meters from the client. In practice, the quality/volume of the chat would stop diminishing after 250 meters, and then immediately cut off when outside the radio range. +- Fixed inability to connect to IPv4 servers when IPv6 is disabled. +- Fixed husk appendage breaking if the character already has extra limbs from e.g. genes. +- Fixed blocked doorways in Alien_Entrance3. +- Fixed Cyrillic symbols not being visible in the server list's server info panel when playing in a language other than Russian. +- Fixed certain genetic effects (such as regeneration from Hammerhead Matriarch genes) not working properly when multiple characters have the same effect. +- Adjusted railgun, coilgun and double coilgun firing offsets to make the projectile spawn closer to the end of the barrel. +- Fixed incorrect tags in the vending machine in EngineeringModule_01_Colony, causing engineering gear to spawn in the output slot. + +Modding: +- Made it possible to check if some value is null or not with PropertyConditionals (e.g. CurrentHull="eq null"). +- Added UseEnvironment.None to Propulsion component. + --------------------------------------------------------------------------------------------------------- v0.20.6.0 --------------------------------------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaTest/GenericToolBoxTests.cs b/Barotrauma/BarotraumaTest/GenericToolBoxTests.cs index 305187f05..ece129035 100644 --- a/Barotrauma/BarotraumaTest/GenericToolBoxTests.cs +++ b/Barotrauma/BarotraumaTest/GenericToolBoxTests.cs @@ -46,6 +46,7 @@ public sealed class GenericToolBoxTests Prop.ForAll(static pair => { ToolBox.StatIdentifierMatches(pair.First, $"{pair.First}~{pair.Second}".ToIdentifier()).Should().BeTrue(); + ToolBox.StatIdentifierMatches($"{pair.First}~{pair.Second}".ToIdentifier(), pair.First).Should().BeTrue(); ToolBox.StatIdentifierMatches(pair.First, pair.First).Should().BeTrue(); ToolBox.StatIdentifierMatches(pair.First, $"{pair.Second}~{pair.First}".ToIdentifier()).Should().BeFalse(); diff --git a/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs b/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs index 1e499dac0..f0bd29a02 100644 --- a/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs +++ b/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Immutable; using System.Linq; using System.Reflection; using Barotrauma; +using Microsoft.Xna.Framework; using Xunit; namespace TestProject; @@ -27,12 +29,56 @@ public class INetSerializableStructImplementationChecks foreach (var type in types) { - var members = NetSerializableProperties.GetPropertiesAndFields(type); + var concreteType = type; + if (type.IsGenericType) + { + // Plug in some known good parameters to evaluate + // a concrete instance of this generic type + + var paramsConstraints = type.GetGenericArguments() + .Select(p => p.GetGenericParameterConstraints()) + .ToImmutableArray(); + + var chosenArgs = new Type[paramsConstraints.Length]; + + for (int i = 0; i < paramsConstraints.Length; i++) + { + var constraints = paramsConstraints[i]; + bool refTypeConstraint = constraints.Any(c + => c.GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)); + bool valueTypeConstraint = constraints.Any(c + => c.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint)); + if (refTypeConstraint && valueTypeConstraint) + { + throw new Exception($"Type \"{type.Name}\" has invalid generic constraints"); + } + + int rngMin = refTypeConstraint ? 3 : 0; + int rngMax = valueTypeConstraint ? 3 : 6; + + chosenArgs[i] = Rand.Range(rngMin, rngMax) switch + { + 0 => typeof(Vector2), + 1 => typeof(Point), + 2 => typeof(int), + + 3 => typeof(string), + 4 => typeof(float[]), + 5 => typeof(int[]), + + var invalid => throw new Exception($"Broken RNG ranges in test, got {invalid}") + }; + } + + concreteType = type.MakeGenericType(chosenArgs); + } + + var members = NetSerializableProperties.GetPropertiesAndFields(concreteType); foreach (var member in members) { void checkType(Type typeBeingChecked) { - Assert.True(tryFindBehavior(typeBeingChecked, out _), $"{type}.{member.Name} of type {member.Type} is unsupported in {nameof(INetSerializableStruct)}"); + Assert.True(tryFindBehavior(typeBeingChecked, out _), $"{concreteType}.{member.Name} of type {member.Type} is unsupported in {nameof(INetSerializableStruct)}"); Type? nestedType = null; if (typeBeingChecked.IsGenericType) {