", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
- .Where(s => s.Contains("left") && s.Contains("top") && s.Contains("width") && s.Contains("height"));
- int id = 0;
- Dictionary
hierarchyToID = new Dictionary();
- Dictionary idToHierarchy = new Dictionary();
- Dictionary idToPositionCode = new Dictionary();
- Dictionary idToName = new Dictionary();
- idToCodeName.Clear();
- foreach (var line in lines)
- {
- var codeNames = new string(line.SkipWhile(c => c != '>').Skip(1).ToArray()).Split(',');
- for (int i = 0; i < codeNames.Length; i++)
- {
- string codeName = codeNames[i].Trim();
- if (string.IsNullOrWhiteSpace(codeName)) { continue; }
- idToCodeName.Add(id, codeName);
- string limbName = new string(codeName.SkipWhile(c => c != '_').Skip(1).ToArray());
- if (string.IsNullOrWhiteSpace(limbName)) { continue; }
- idToName.Add(id, limbName);
- var parts = line.Split(' ');
- int ParseToInt(string selector)
- {
- string part = parts.First(p => p.Contains(selector));
- string s = new string(part.SkipWhile(c => c != ':').Skip(1).TakeWhile(c => char.IsNumber(c)).ToArray());
- int.TryParse(s, out int v);
- return v;
- };
- // example: 111311cr -> 111311
- string hierarchy = new string(codeName.TakeWhile(c => char.IsNumber(c)).ToArray());
- if (hierarchyToID.ContainsKey(hierarchy))
- {
- DebugConsole.ThrowError(GetCharacterEditorTranslation("MultipleItemsWithSameHierarchy").Replace("[hierarchy]", hierarchy).Replace("[name]", codeName));
- return;
- }
- hierarchyToID.Add(hierarchy, id);
- idToHierarchy.Add(id, hierarchy);
- string positionCode = new string(codeName.SkipWhile(c => char.IsNumber(c)).TakeWhile(c => c != '_').ToArray());
- idToPositionCode.Add(id, positionCode.ToLowerInvariant());
- int x = ParseToInt("left");
- int y = ParseToInt("top");
- int width = ParseToInt("width");
- int height = ParseToInt("height");
- // This is overridden when the data is loaded from the gui fields.
- LimbXElements.Add(hierarchy, new XElement("limb",
- new XAttribute("id", id),
- new XAttribute("name", limbName),
- new XAttribute("type", ParseLimbType(limbName).ToString()),
- new XElement("sprite",
- new XAttribute("texture", ""),
- new XAttribute("sourcerect", $"{x}, {y}, {width}, {height}"))
- ));
- limbCallback?.Invoke(id, limbName, ParseLimbType(limbName), new Rectangle(x, y, width, height));
- id++;
- }
- }
- for (int i = 0; i < id; i++)
- {
- if (idToHierarchy.TryGetValue(i, out string hierarchy))
- {
- if (hierarchy != "0")
- {
- // NEW LOGIC: if hierarchy length == 1, parent to 0
- // Else parent to the last bone in the current hierarchy (11 is parented to 1, 212 is parented to 21 etc)
- string parent = hierarchy.Length > 1 ? hierarchy.Remove(hierarchy.Length - 1, 1) : "0";
- if (hierarchyToID.TryGetValue(parent, out int parentID))
- {
- Vector2 anchor1 = Vector2.Zero;
- Vector2 anchor2 = Vector2.Zero;
- idToName.TryGetValue(parentID, out string parentName);
- idToName.TryGetValue(i, out string limbName);
- string jointName = $"{GetCharacterEditorTranslation("Joint")} {parentName} - {limbName}";
- if (idToPositionCode.TryGetValue(i, out string positionCode))
- {
- float scalar = 0.8f;
- if (LimbXElements.TryGetValue(parent, out XElement parentElement))
- {
- Rectangle parentSourceRect = parentElement.Element("sprite").GetAttributeRect("sourcerect", Rectangle.Empty);
- float parentWidth = parentSourceRect.Width / 2 * scalar;
- float parentHeight = parentSourceRect.Height / 2 * scalar;
- switch (positionCode)
- {
- case "tl": // -1, 1
- anchor1 = new Vector2(-parentWidth, parentHeight);
- break;
- case "tc": // 0, 1
- anchor1 = new Vector2(0, parentHeight);
- break;
- case "tr": // -1, 1
- anchor1 = new Vector2(-parentWidth, parentHeight);
- break;
- case "cl": // -1, 0
- anchor1 = new Vector2(-parentWidth, 0);
- break;
- case "cr": // 1, 0
- anchor1 = new Vector2(parentWidth, 0);
- break;
- case "bl": // -1, -1
- anchor1 = new Vector2(-parentWidth, -parentHeight);
- break;
- case "bc": // 0, -1
- anchor1 = new Vector2(0, -parentHeight);
- break;
- case "br": // 1, -1
- anchor1 = new Vector2(parentWidth, -parentHeight);
- break;
- }
- if (LimbXElements.TryGetValue(hierarchy, out XElement element))
- {
- Rectangle sourceRect = element.Element("sprite").GetAttributeRect("sourcerect", Rectangle.Empty);
- float width = sourceRect.Width / 2 * scalar;
- float height = sourceRect.Height / 2 * scalar;
- switch (positionCode)
- {
- // Inverse
- case "tl":
- // br
- anchor2 = new Vector2(-width, -height);
- break;
- case "tc":
- // bc
- anchor2 = new Vector2(0, -height);
- break;
- case "tr":
- // bl
- anchor2 = new Vector2(-width, -height);
- break;
- case "cl":
- // cr
- anchor2 = new Vector2(width, 0);
- break;
- case "cr":
- // cl
- anchor2 = new Vector2(-width, 0);
- break;
- case "bl":
- // tr
- anchor2 = new Vector2(-width, height);
- break;
- case "bc":
- // tc
- anchor2 = new Vector2(0, height);
- break;
- case "br":
- // tl
- anchor2 = new Vector2(-width, height);
- break;
- }
- }
- }
- }
- // This is overridden when the data is loaded from the gui fields.
- JointXElements.Add(new XElement("joint",
- new XAttribute("name", jointName),
- new XAttribute("limb1", parentID),
- new XAttribute("limb2", i),
- new XAttribute("limb1anchor", $"{anchor1.X.Format(2)}, {anchor1.Y.Format(2)}"),
- new XAttribute("limb2anchor", $"{anchor2.X.Format(2)}, {anchor2.Y.Format(2)}")
- ));
- jointCallback?.Invoke(parentID, i, anchor1, anchor2, jointName);
- }
- }
- }
- }
- }
-
protected LimbType ParseLimbType(string limbName)
{
var limbType = LimbType.None;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs
index e89550ec1..0ae5127d6 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs
@@ -269,7 +269,7 @@ namespace Barotrauma
};
#if USE_STEAM
- steamWorkshopButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SteamWorkshopButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
+ steamWorkshopButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("settingstab.mods"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
Enabled = true,
@@ -463,13 +463,17 @@ namespace Barotrauma
}
};
var tutorialPreview = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), tutorialContent.RectTransform)) { RelativeSpacing = 0.05f, Stretch = true };
- var imageContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f), tutorialPreview.RectTransform), style: "InnerFrame");
+ var imageContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "InnerFrame");
tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: true);
- var infoContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.4f), tutorialPreview.RectTransform), style: "GUIFrameListBox");
- var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), infoContainer.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter);
+ var infoContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "GUIFrameListBox");
+ var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), infoContainer.RectTransform, Anchor.Center), childAnchor: Anchor.TopLeft)
+ {
+ AbsoluteSpacing = GUI.IntScale(10)
+ };
- tutorialHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.75f), infoContent.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center);
+ tutorialHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont);
+ tutorialDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), string.Empty, wrap: true);
var startButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.0f), infoContent.RectTransform, Anchor.BottomRight), text: TextManager.Get("startgamebutton"))
{
@@ -500,6 +504,10 @@ namespace Barotrauma
private void SelectTutorial(Tutorial tutorial)
{
tutorialHeader.Text = tutorial.DisplayName;
+ tutorialHeader.CalculateHeightFromText();
+ tutorialDescription.Text = tutorial.Description;
+ tutorialDescription.CalculateHeightFromText();
+ (tutorialDescription.Parent as GUILayoutGroup)?.Recalculate();
tutorial.TutorialPrefab.Banner?.EnsureLazyLoaded();
tutorialBanner.Sprite = tutorial.TutorialPrefab.Banner;
tutorialBanner.Color = tutorial.TutorialPrefab.Banner == null ? Color.Black : Color.White;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs
index aa6c37369..c8366b171 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs
@@ -2487,7 +2487,7 @@ namespace Barotrauma
{
IntValue = MainSub.Info.Tier,
MinValueInt = 1,
- MaxValueInt = 3,
+ MaxValueInt = SubmarineInfo.HighestTier,
OnValueChanged = (numberInput) =>
{
MainSub.Info.Tier = numberInput.IntValue;
@@ -2495,7 +2495,7 @@ namespace Barotrauma
};
if (MainSub?.Info != null)
{
- MainSub.Info.Tier = Math.Clamp(MainSub.Info.Tier, 1, 3);
+ MainSub.Info.Tier = Math.Clamp(MainSub.Info.Tier, 1, SubmarineInfo.HighestTier);
}
var crewSizeArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true)
@@ -3228,7 +3228,7 @@ namespace Barotrauma
}
string pathWithoutUserName = Path.GetFullPath(sub.FilePath);
- string saveFolder = Path.GetFullPath(SaveUtil.SaveFolder);
+ string saveFolder = Path.GetFullPath(SaveUtil.DefaultSaveFolder);
if (pathWithoutUserName.StartsWith(saveFolder))
{
pathWithoutUserName = "..." + pathWithoutUserName[saveFolder.Length..];
@@ -4217,7 +4217,8 @@ namespace Barotrauma
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center))
{
PlaySoundOnSelect = true,
- OnSelected = SelectWire
+ OnSelected = SelectWire,
+ CanTakeKeyBoardFocus = false
};
List wirePrefabs = new List();
@@ -5866,7 +5867,7 @@ namespace Barotrauma
decimal realWorldDistance = decimal.Round((decimal) (Vector2.Distance(startPos, mouseWorldPos) * Physics.DisplayToRealWorldRatio), 2);
Vector2 offset = new Vector2(GUI.IntScale(24));
- GUI.DrawString(spriteBatch, PlayerInput.MousePosition + offset, $"{realWorldDistance}m", GUIStyle.TextColorNormal, font: GUIStyle.SubHeadingFont, backgroundColor: Color.Black, backgroundPadding: 4);
+ GUI.DrawString(spriteBatch, PlayerInput.MousePosition + offset, $"{realWorldDistance} m", GUIStyle.TextColorNormal, font: GUIStyle.Font, backgroundColor: Color.Black, backgroundPadding: 4);
}
spriteBatch.End();
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs
index 65a6e87bc..594e36ad4 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs
@@ -176,7 +176,7 @@ namespace Barotrauma
Action setter) where T : Enum
=> Dropdown(parent, textFunc, tooltipFunc, (T[])Enum.GetValues(typeof(T)), currentValue, setter);
- private static void Dropdown(GUILayoutGroup parent, Func textFunc, Func? tooltipFunc, IReadOnlyList values, T currentValue, Action setter)
+ private static GUIDropDown Dropdown(GUILayoutGroup parent, Func textFunc, Func? tooltipFunc, IReadOnlyList values, T currentValue, Action setter)
{
var dropdown = new GUIDropDown(NewItemRectT(parent));
values.ForEach(v => dropdown.AddItem(text: textFunc(v), userData: v, toolTip: tooltipFunc?.Invoke(v) ?? null));
@@ -189,9 +189,10 @@ namespace Barotrauma
setter((T)obj);
return true;
};
+ return dropdown;
}
- private void Slider(GUILayoutGroup parent, Vector2 range, int steps, Func labelFunc, float currentValue, Action setter, LocalizedString? tooltip = null)
+ private static (GUIScrollBar slider, GUITextBlock label) Slider(GUILayoutGroup parent, Vector2 range, int steps, Func labelFunc, float currentValue, Action setter, LocalizedString? tooltip = null)
{
var layout = new GUILayoutGroup(NewItemRectT(parent), isHorizontal: true);
var slider = new GUIScrollBar(new RectTransform((0.72f, 1.0f), layout.RectTransform), style: "GUISlider")
@@ -213,11 +214,12 @@ namespace Barotrauma
setter(sb.BarScrollValue);
return true;
};
+ return (slider, label);
}
- private void Tickbox(GUILayoutGroup parent, LocalizedString label, LocalizedString tooltip, bool currentValue, Action setter)
+ private static GUITickBox Tickbox(GUILayoutGroup parent, LocalizedString label, LocalizedString tooltip, bool currentValue, Action setter)
{
- var tickbox = new GUITickBox(NewItemRectT(parent), label)
+ return new GUITickBox(NewItemRectT(parent), label)
{
Selected = currentValue,
ToolTip = tooltip,
@@ -231,7 +233,7 @@ namespace Barotrauma
private string Percentage(float v) => ToolBox.GetFormattedPercentage(v);
- private int Round(float v) => (int)MathF.Round(v);
+ private static int Round(float v) => MathUtils.RoundToInt(v);
private void CreateGraphicsTab()
{
@@ -262,30 +264,30 @@ namespace Barotrauma
Spacer(left);
Label(left, TextManager.Get("DisplayMode"), GUIStyle.SubHeadingFont);
- DropdownEnum(left, (m) => TextManager.Get($"{m}"), null, unsavedConfig.Graphics.DisplayMode, (v) => unsavedConfig.Graphics.DisplayMode = v);
+ DropdownEnum(left, (m) => TextManager.Get($"{m}"), null, unsavedConfig.Graphics.DisplayMode, v => unsavedConfig.Graphics.DisplayMode = v);
Spacer(left);
- Tickbox(left, TextManager.Get("EnableVSync"), TextManager.Get("EnableVSyncTooltip"), unsavedConfig.Graphics.VSync, (v) => unsavedConfig.Graphics.VSync = v);
- Tickbox(left, TextManager.Get("EnableTextureCompression"), TextManager.Get("EnableTextureCompressionTooltip"), unsavedConfig.Graphics.CompressTextures, (v) => unsavedConfig.Graphics.CompressTextures = v);
+ Tickbox(left, TextManager.Get("EnableVSync"), TextManager.Get("EnableVSyncTooltip"), unsavedConfig.Graphics.VSync, v => unsavedConfig.Graphics.VSync = v);
+ Tickbox(left, TextManager.Get("EnableTextureCompression"), TextManager.Get("EnableTextureCompressionTooltip"), unsavedConfig.Graphics.CompressTextures, v => unsavedConfig.Graphics.CompressTextures = v);
Label(right, TextManager.Get("LOSEffect"), GUIStyle.SubHeadingFont);
- DropdownEnum(right, (m) => TextManager.Get($"LosMode{m}"), null, unsavedConfig.Graphics.LosMode, (v) => unsavedConfig.Graphics.LosMode = v);
+ DropdownEnum(right, (m) => TextManager.Get($"LosMode{m}"), null, unsavedConfig.Graphics.LosMode, v => unsavedConfig.Graphics.LosMode = v);
Spacer(right);
Label(right, TextManager.Get("LightMapScale"), GUIStyle.SubHeadingFont);
- Slider(right, (0.5f, 1.0f), 11, (v) => TextManager.GetWithVariable("percentageformat", "[value]", Round(v * 100).ToString()).Value, unsavedConfig.Graphics.LightMapScale, (v) => unsavedConfig.Graphics.LightMapScale = v, TextManager.Get("LightMapScaleTooltip"));
+ Slider(right, (0.5f, 1.0f), 11, v => TextManager.GetWithVariable("percentageformat", "[value]", Round(v * 100).ToString()).Value, unsavedConfig.Graphics.LightMapScale, v => unsavedConfig.Graphics.LightMapScale = v, TextManager.Get("LightMapScaleTooltip"));
Spacer(right);
Label(right, TextManager.Get("VisibleLightLimit"), GUIStyle.SubHeadingFont);
- Slider(right, (10, 210), 21, (v) => v > 200 ? TextManager.Get("unlimited").Value : Round(v).ToString(), unsavedConfig.Graphics.VisibleLightLimit,
- (v) => unsavedConfig.Graphics.VisibleLightLimit = v > 200 ? int.MaxValue : Round(v), TextManager.Get("VisibleLightLimitTooltip"));
+ Slider(right, (10, 210), 21, v => v > 200 ? TextManager.Get("unlimited").Value : Round(v).ToString(), unsavedConfig.Graphics.VisibleLightLimit,
+ v => unsavedConfig.Graphics.VisibleLightLimit = v > 200 ? int.MaxValue : Round(v), TextManager.Get("VisibleLightLimitTooltip"));
Spacer(right);
- Tickbox(right, TextManager.Get("RadialDistortion"), TextManager.Get("RadialDistortionTooltip"), unsavedConfig.Graphics.RadialDistortion, (v) => unsavedConfig.Graphics.RadialDistortion = v);
- Tickbox(right, TextManager.Get("ChromaticAberration"), TextManager.Get("ChromaticAberrationTooltip"), unsavedConfig.Graphics.ChromaticAberration, (v) => unsavedConfig.Graphics.ChromaticAberration = v);
+ Tickbox(right, TextManager.Get("RadialDistortion"), TextManager.Get("RadialDistortionTooltip"), unsavedConfig.Graphics.RadialDistortion, v => unsavedConfig.Graphics.RadialDistortion = v);
+ Tickbox(right, TextManager.Get("ChromaticAberration"), TextManager.Get("ChromaticAberrationTooltip"), unsavedConfig.Graphics.ChromaticAberration, v => unsavedConfig.Graphics.ChromaticAberration = v);
Label(right, TextManager.Get("ParticleLimit"), GUIStyle.SubHeadingFont);
- Slider(right, (100, 1500), 15, (v) => Round(v).ToString(), unsavedConfig.Graphics.ParticleLimit, (v) => unsavedConfig.Graphics.ParticleLimit = Round(v));
+ Slider(right, (100, 1500), 15, v => Round(v).ToString(), unsavedConfig.Graphics.ParticleLimit, v => unsavedConfig.Graphics.ParticleLimit = Round(v));
Spacer(right);
}
@@ -399,23 +401,23 @@ namespace Barotrauma
Spacer(audio);
Label(audio, TextManager.Get("SoundVolume"), GUIStyle.SubHeadingFont);
- Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.SoundVolume, (v) => unsavedConfig.Audio.SoundVolume = v);
+ Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.SoundVolume, v => unsavedConfig.Audio.SoundVolume = v);
Label(audio, TextManager.Get("MusicVolume"), GUIStyle.SubHeadingFont);
- Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.MusicVolume, (v) => unsavedConfig.Audio.MusicVolume = v);
+ Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.MusicVolume, v => unsavedConfig.Audio.MusicVolume = v);
Label(audio, TextManager.Get("UiSoundVolume"), GUIStyle.SubHeadingFont);
- Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.UiVolume, (v) => unsavedConfig.Audio.UiVolume = v);
+ Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.UiVolume, v => unsavedConfig.Audio.UiVolume = v);
- Tickbox(audio, TextManager.Get("MuteOnFocusLost"), TextManager.Get("MuteOnFocusLostTooltip"), unsavedConfig.Audio.MuteOnFocusLost, (v) => unsavedConfig.Audio.MuteOnFocusLost = v);
- Tickbox(audio, TextManager.Get("DynamicRangeCompression"), TextManager.Get("DynamicRangeCompressionTooltip"), unsavedConfig.Audio.DynamicRangeCompressionEnabled, (v) => unsavedConfig.Audio.DynamicRangeCompressionEnabled = v);
+ Tickbox(audio, TextManager.Get("MuteOnFocusLost"), TextManager.Get("MuteOnFocusLostTooltip"), unsavedConfig.Audio.MuteOnFocusLost, v => unsavedConfig.Audio.MuteOnFocusLost = v);
+ Tickbox(audio, TextManager.Get("DynamicRangeCompression"), TextManager.Get("DynamicRangeCompressionTooltip"), unsavedConfig.Audio.DynamicRangeCompressionEnabled, v => unsavedConfig.Audio.DynamicRangeCompressionEnabled = v);
Spacer(audio);
Label(audio, TextManager.Get("VoiceChatVolume"), GUIStyle.SubHeadingFont);
- Slider(audio, (0, 2), 201, Percentage, unsavedConfig.Audio.VoiceChatVolume, (v) => unsavedConfig.Audio.VoiceChatVolume = v);
+ Slider(audio, (0, 2), 201, Percentage, unsavedConfig.Audio.VoiceChatVolume, v => unsavedConfig.Audio.VoiceChatVolume = v);
- Tickbox(audio, TextManager.Get("DirectionalVoiceChat"), TextManager.Get("DirectionalVoiceChatTooltip"), unsavedConfig.Audio.UseDirectionalVoiceChat, (v) => unsavedConfig.Audio.UseDirectionalVoiceChat = v);
- Tickbox(audio, TextManager.Get("VoipAttenuation"), TextManager.Get("VoipAttenuationTooltip"), unsavedConfig.Audio.VoipAttenuationEnabled, (v) => unsavedConfig.Audio.VoipAttenuationEnabled = v);
+ Tickbox(audio, TextManager.Get("DirectionalVoiceChat"), TextManager.Get("DirectionalVoiceChatTooltip"), unsavedConfig.Audio.UseDirectionalVoiceChat, v => unsavedConfig.Audio.UseDirectionalVoiceChat = v);
+ Tickbox(audio, TextManager.Get("VoipAttenuation"), TextManager.Get("VoipAttenuationTooltip"), unsavedConfig.Audio.VoipAttenuationEnabled, v => unsavedConfig.Audio.VoipAttenuationEnabled = v);
Label(voiceChat, TextManager.Get("AudioInputDevice"), GUIStyle.SubHeadingFont);
@@ -424,7 +426,7 @@ namespace Barotrauma
Spacer(voiceChat);
Label(voiceChat, TextManager.Get("VCInputMode"), GUIStyle.SubHeadingFont);
- DropdownEnum(voiceChat, (v) => TextManager.Get($"VoiceMode.{v}"), (v) => TextManager.Get($"VoiceMode.{v}Tooltip"), unsavedConfig.Audio.VoiceSetting, (v) => unsavedConfig.Audio.VoiceSetting = v);
+ DropdownEnum(voiceChat, v => TextManager.Get($"VoiceMode.{v}"), v => TextManager.Get($"VoiceMode.{v}Tooltip"), unsavedConfig.Audio.VoiceSetting, v => unsavedConfig.Audio.VoiceSetting = v);
Spacer(voiceChat);
var noiseGateThresholdLabel = Label(voiceChat, TextManager.Get("NoiseGateThreshold"), GUIStyle.SubHeadingFont);
@@ -464,11 +466,11 @@ namespace Barotrauma
Spacer(voiceChat);
Label(voiceChat, TextManager.Get("MicrophoneVolume"), GUIStyle.SubHeadingFont);
- Slider(voiceChat, (0, 10), 101, Percentage, unsavedConfig.Audio.MicrophoneVolume, (v) => unsavedConfig.Audio.MicrophoneVolume = v);
+ Slider(voiceChat, (0, 10), 101, Percentage, unsavedConfig.Audio.MicrophoneVolume, v => unsavedConfig.Audio.MicrophoneVolume = v);
Spacer(voiceChat);
Label(voiceChat, TextManager.Get("CutoffPrevention"), GUIStyle.SubHeadingFont);
- Slider(voiceChat, (0, 500), 26, (v) => $"{Round(v)} ms", unsavedConfig.Audio.VoiceChatCutoffPrevention, (v) => unsavedConfig.Audio.VoiceChatCutoffPrevention = Round(v), TextManager.Get("CutoffPreventionTooltip"));
+ Slider(voiceChat, (0, 500), 26, v => $"{Round(v)} ms", unsavedConfig.Audio.VoiceChatCutoffPrevention, v => unsavedConfig.Audio.VoiceChatCutoffPrevention = Round(v), TextManager.Get("CutoffPreventionTooltip"));
}
private readonly Dictionary> inputButtonValueNameGetters = new Dictionary>();
@@ -481,8 +483,10 @@ namespace Barotrauma
GUILayoutGroup layout = CreateCenterLayout(content);
Label(layout, TextManager.Get("AimAssist"), GUIStyle.SubHeadingFont);
- Slider(layout, (0, 1), 101, Percentage, unsavedConfig.AimAssistAmount, (v) => unsavedConfig.AimAssistAmount = v, TextManager.Get("AimAssistTooltip"));
- Tickbox(layout, TextManager.Get("EnableMouseLook"), TextManager.Get("EnableMouseLookTooltip"), unsavedConfig.EnableMouseLook, (v) => unsavedConfig.EnableMouseLook = v);
+
+ var aimAssistSlider = Slider(layout, (0, 1), 101, Percentage, unsavedConfig.AimAssistAmount, v => unsavedConfig.AimAssistAmount = v, TextManager.Get("AimAssistTooltip"));
+ Tickbox(layout, TextManager.Get("EnableMouseLook"), TextManager.Get("EnableMouseLookTooltip"), unsavedConfig.EnableMouseLook, v => unsavedConfig.EnableMouseLook = v);
+
Spacer(layout);
GUIListBox keyMapList =
@@ -523,7 +527,7 @@ namespace Barotrauma
if (willBeSelected)
{
inputBoxSelectedThisFrame = true;
- currentSetter = (v) =>
+ currentSetter = v =>
{
valueSetter(v);
btn.Text = valueNameGetter();
@@ -626,7 +630,7 @@ namespace Barotrauma
currRow,
TextManager.Get($"InputType.{input}"),
() => unsavedConfig.KeyMap.Bindings[input].Name,
- (v) => unsavedConfig.KeyMap = unsavedConfig.KeyMap.WithBinding(input, v),
+ v => unsavedConfig.KeyMap = unsavedConfig.KeyMap.WithBinding(input, v),
LegacyInputTypes.Contains(input));
}
}
@@ -644,7 +648,7 @@ namespace Barotrauma
currRow,
TextManager.GetWithVariable("inventoryslotkeybind", "[slotnumber]", (currIndex + 1).ToString(CultureInfo.InvariantCulture)),
() => unsavedConfig.InventoryKeyMap.Bindings[currIndex].Name,
- (v) => unsavedConfig.InventoryKeyMap = unsavedConfig.InventoryKeyMap.WithBinding(currIndex, v));
+ v => unsavedConfig.InventoryKeyMap = unsavedConfig.InventoryKeyMap.WithBinding(currIndex, v));
}
}
@@ -663,6 +667,8 @@ namespace Barotrauma
{
unsavedConfig.InventoryKeyMap = GameSettings.Config.InventoryKeyMapping.GetDefault();
unsavedConfig.KeyMap = GameSettings.Config.KeyMapping.GetDefault();
+ aimAssistSlider.slider.BarScrollValue = GameSettings.Config.DefaultAimAssist;
+ aimAssistSlider.label.Text = Percentage(GameSettings.Config.DefaultAimAssist);
foreach (var btn in inputButtonValueNameGetters.Keys)
{
btn.Text = inputButtonValueNameGetters[btn]();
@@ -683,13 +689,13 @@ namespace Barotrauma
.OrderBy(l => TextManager.GetTranslatedLanguageName(l).ToIdentifier())
.ToArray();
Label(layout, TextManager.Get("Language"), GUIStyle.SubHeadingFont);
- Dropdown(layout, (v) => TextManager.GetTranslatedLanguageName(v), null, languages, unsavedConfig.Language, (v) => unsavedConfig.Language = v);
+ Dropdown(layout, v => TextManager.GetTranslatedLanguageName(v), null, languages, unsavedConfig.Language, v => unsavedConfig.Language = v);
Spacer(layout);
- Tickbox(layout, TextManager.Get("PauseOnFocusLost"), TextManager.Get("PauseOnFocusLostTooltip"), unsavedConfig.PauseOnFocusLost, (v) => unsavedConfig.PauseOnFocusLost = v);
+ Tickbox(layout, TextManager.Get("PauseOnFocusLost"), TextManager.Get("PauseOnFocusLostTooltip"), unsavedConfig.PauseOnFocusLost, v => unsavedConfig.PauseOnFocusLost = v);
Spacer(layout);
- Tickbox(layout, TextManager.Get("DisableInGameHints"), TextManager.Get("DisableInGameHintsTooltip"), unsavedConfig.DisableInGameHints, (v) => unsavedConfig.DisableInGameHints = v);
+ Tickbox(layout, TextManager.Get("DisableInGameHints"), TextManager.Get("DisableInGameHintsTooltip"), unsavedConfig.DisableInGameHints, v => unsavedConfig.DisableInGameHints = v);
var resetInGameHintsButton =
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), layout.RectTransform),
TextManager.Get("ResetInGameHints"), style: "GUIButtonSmall")
@@ -710,13 +716,17 @@ namespace Barotrauma
}
};
Spacer(layout);
-
+
+ Label(layout, TextManager.Get("ShowEnemyHealthBars"), GUIStyle.SubHeadingFont);
+ DropdownEnum(layout, v => TextManager.Get($"ShowEnemyHealthBars.{v}"), null, unsavedConfig.ShowEnemyHealthBars, v => unsavedConfig.ShowEnemyHealthBars = v);
+ Spacer(layout);
+
Label(layout, TextManager.Get("HUDScale"), GUIStyle.SubHeadingFont);
- Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.HUDScale, (v) => unsavedConfig.Graphics.HUDScale = v);
+ Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.HUDScale, v => unsavedConfig.Graphics.HUDScale = v);
Label(layout, TextManager.Get("InventoryScale"), GUIStyle.SubHeadingFont);
- Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.InventoryScale, (v) => unsavedConfig.Graphics.InventoryScale = v);
+ Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.InventoryScale, v => unsavedConfig.Graphics.InventoryScale = v);
Label(layout, TextManager.Get("TextScale"), GUIStyle.SubHeadingFont);
- Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.TextScale, (v) => unsavedConfig.Graphics.TextScale = v);
+ Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.TextScale, v => unsavedConfig.Graphics.TextScale = v);
#if !OSX
Spacer(layout);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs
index 54cab99e7..258594f3d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs
@@ -50,20 +50,25 @@ namespace Barotrauma.Steam
lobbyState = LobbyState.Owner;
lobbyID = (currentLobby?.Id).Value;
- if (serverSettings.IsPublic)
- {
- currentLobby?.SetPublic();
- }
- else
- {
- currentLobby?.SetFriendsOnly();
- }
+ SetLobbyPublic(serverSettings.IsPublic);
currentLobby?.SetJoinable(true);
UpdateLobby(serverSettings);
});
}
+ public static void SetLobbyPublic(bool isPublic)
+ {
+ if (isPublic)
+ {
+ currentLobby?.SetPublic();
+ }
+ else
+ {
+ currentLobby?.SetFriendsOnly();
+ }
+ }
+
public static void UpdateLobby(ServerSettings serverSettings)
{
if (GameMain.Client == null)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/SpreadsheetExport.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/SpreadsheetExport.cs
index 2d4a1ffb2..67bc81568 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Utils/SpreadsheetExport.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/SpreadsheetExport.cs
@@ -104,7 +104,7 @@ namespace Barotrauma
}
}
- List successEffects = statusEffects.Where(se => se.type == ActionType.OnUse).ToList();
+ List successEffects = statusEffects.Where(se => se.type == ActionType.OnSuccess).ToList();
List failureEffects = statusEffects.Where(se => se.type == ActionType.OnFailure).ToList();
foreach (StatusEffect statusEffect in successEffects)
diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj
index 41e27b185..92a7c8a82 100644
--- a/Barotrauma/BarotraumaClient/LinuxClient.csproj
+++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj
@@ -6,7 +6,7 @@
Barotrauma
FakeFish, Undertow Games
Barotrauma
- 0.20.0.0
+ 0.20.4.0
Copyright © FakeFish 2018-2022
AnyCPU;x64
Barotrauma
@@ -100,6 +100,8 @@
PreserveNewest
+
+
Icon.bmp
diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj
index 24e07ca66..2a8218f30 100644
--- a/Barotrauma/BarotraumaClient/MacClient.csproj
+++ b/Barotrauma/BarotraumaClient/MacClient.csproj
@@ -6,7 +6,7 @@
Barotrauma
FakeFish, Undertow Games
Barotrauma
- 0.20.0.0
+ 0.20.4.0
Copyright © FakeFish 2018-2022
AnyCPU;x64
Barotrauma
@@ -99,6 +99,7 @@
PreserveNewest
+
Icon.bmp
diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj
index 4b80f7c8d..77fedbce5 100644
--- a/Barotrauma/BarotraumaClient/WindowsClient.csproj
+++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj
@@ -6,7 +6,7 @@
Barotrauma
FakeFish, Undertow Games
Barotrauma
- 0.20.0.0
+ 0.20.4.0
Copyright © FakeFish 2018-2022
AnyCPU;x64
Barotrauma
@@ -117,6 +117,7 @@
+
@@ -126,6 +127,7 @@
+
diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj
index 7e7a57032..38f6eccc6 100644
--- a/Barotrauma/BarotraumaServer/LinuxServer.csproj
+++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj
@@ -6,7 +6,7 @@
Barotrauma
FakeFish, Undertow Games
Barotrauma Dedicated Server
- 0.20.0.0
+ 0.20.4.0
Copyright © FakeFish 2018-2022
AnyCPU;x64
DedicatedServer
diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj
index 34e6882e6..56a08b7b4 100644
--- a/Barotrauma/BarotraumaServer/MacServer.csproj
+++ b/Barotrauma/BarotraumaServer/MacServer.csproj
@@ -6,7 +6,7 @@
Barotrauma
FakeFish, Undertow Games
Barotrauma Dedicated Server
- 0.20.0.0
+ 0.20.4.0
Copyright © FakeFish 2018-2022
AnyCPU;x64
DedicatedServer
diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs
index a80e80caa..eedb1f10b 100644
--- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs
+++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs
@@ -269,10 +269,13 @@ namespace Barotrauma
case EventType.UpdateTalents:
if (c.Character != this)
{
+ if (!IsBot || !c.HasPermission(ClientPermissions.ManageBotTalents))
+ {
#if DEBUG
- DebugConsole.Log("Received a character update message from a client who's not controlling the character");
+ DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif
- return;
+ return;
+ }
}
// get the full list of talents from the player, only give the ones
diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs
index dfa4bc4c0..5d9dde87c 100644
--- a/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs
+++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs
@@ -31,7 +31,7 @@ namespace Barotrauma
if (convAction.SelectedOption > -1)
{
//someone else already chose an option for this conversation: interrupt for this client
- convAction.ServerWrite(convAction.speaker, sender, interrupt: true);
+ convAction.ServerWrite(convAction.Speaker, sender, interrupt: true);
}
else
{
diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs
index e30b1148f..e46134ca6 100644
--- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs
+++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs
@@ -2,7 +2,9 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
+using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma
@@ -30,6 +32,9 @@ namespace Barotrauma
switch (header)
{
+ case NetworkHeader.ADD_EVERYTHING_TO_PENDING:
+ ProcessAddEverything(sender);
+ break;
case NetworkHeader.REQUEST_AFFLICTIONS:
ProcessRequestedAfflictions(inc, sender);
break;
@@ -57,7 +62,14 @@ namespace Barotrauma
NetCrewMember newCrewMember = INetSerializableStruct.Read(inc);
InsertPendingCrewMember(newCrewMember);
- ServerSend(newCrewMember, NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable, reponseClient: client);
+ ServerSend(new NetCollection(newCrewMember), NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable, reponseClient: client);
+ }
+
+ private void ProcessAddEverything(Client client)
+ {
+ if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
+ AddEverythingToPending();
+ ServerSend(PendingHeals.ToNetCollection(), NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable, reponseClient: client);
}
private void ProcessNewRemoval(IReadMessage inc, Client client)
@@ -73,12 +85,7 @@ namespace Barotrauma
{
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
- INetSerializableStruct writeCrewMember = new NetPendingCrew
- {
- CrewMembers = PendingHeals.ToArray()
- };
-
- ServerSend(writeCrewMember, NetworkHeader.REQUEST_PENDING, DeliveryMethod.Reliable, targetClient: client);
+ ServerSend(PendingHeals.ToNetCollection(), NetworkHeader.REQUEST_PENDING, DeliveryMethod.Reliable, targetClient: client);
}
private void ProcessHealing(Client client)
@@ -107,10 +114,10 @@ namespace Barotrauma
CharacterInfo? foundInfo = crewMember.FindCharacterInfo(GetCrewCharacters());
- NetAffliction[] pendingAfflictions = Array.Empty();
+ ImmutableArray pendingAfflictions = ImmutableArray.Empty;
int infoId = 0;
- if (foundInfo is { Character: { CharacterHealth: { } health } })
+ if (foundInfo is { Character.CharacterHealth: { } health })
{
pendingAfflictions = GetAllAfflictions(health);
infoId = foundInfo.GetIdentifierUsingOriginalName();
diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs
index e48d786f1..666cd3039 100644
--- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs
+++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs
@@ -2516,6 +2516,7 @@ namespace Barotrauma.Networking
msg.WriteInt32(ServerSettings.MaximumMoneyTransferRequest);
msg.WriteBoolean(IsUsingRespawnShuttle());
msg.WriteByte((byte)ServerSettings.LosMode);
+ msg.WriteByte((byte)ServerSettings.ShowEnemyHealthBars);
msg.WriteBoolean(includesFinalize); msg.WritePadBits();
ServerSettings.WriteMonsterEnabled(msg);
diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs
index 603683348..7e348523a 100644
--- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs
+++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs
@@ -272,7 +272,6 @@ namespace Barotrauma.Networking
XDocument doc = new XDocument(new XElement("serversettings"));
doc.Root.SetAttributeValue("name", ServerName);
- doc.Root.SetAttributeValue("public", IsPublic);
doc.Root.SetAttributeValue("port", Port);
#if USE_STEAM
doc.Root.SetAttributeValue("queryport", QueryPort);
diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj
index f69fca38a..575b0e435 100644
--- a/Barotrauma/BarotraumaServer/WindowsServer.csproj
+++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj
@@ -6,7 +6,7 @@
Barotrauma
FakeFish, Undertow Games
Barotrauma Dedicated Server
- 0.20.0.0
+ 0.20.4.0
Copyright © FakeFish 2018-2022
AnyCPU;x64
DedicatedServer
diff --git a/Barotrauma/BarotraumaShared/Data/campaignsettings.xml b/Barotrauma/BarotraumaShared/Data/campaignsettings.xml
index d88b556aa..40605ddf4 100644
--- a/Barotrauma/BarotraumaShared/Data/campaignsettings.xml
+++ b/Barotrauma/BarotraumaShared/Data/campaignsettings.xml
@@ -7,18 +7,21 @@
+ permissions="ManageRound,Kick,SelectSub,SelectMode,ManageCampaign,ConsoleCommands,ServerLog,ManageSettings,ManageMoney,ManageBotTalents">
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs
index 4cf5983f2..f3ac3917c 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs
@@ -1836,8 +1836,7 @@ namespace Barotrauma
float GetTargetMaxSpeed() => Character.ApplyTemporarySpeedLimits(Character.AnimController.CurrentSwimParams.MovementSpeed * 0.3f);
}
}
-
- if (AttackLimb is Limb attackLimb && attackLimb.attack.Ranged)
+ if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && AttackLimb is Limb attackLimb && attackLimb.attack.Ranged)
{
bool advance = !canAttack && Character.InWater || distance > attackLimb.attack.Range * 0.9f;
bool fallBack = canAttack && distance < Math.Min(250, attackLimb.attack.Range * 0.25f);
@@ -1881,19 +1880,11 @@ namespace Barotrauma
}
}
}
- IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity as IDamageable;
- if (AttackLimb?.attack is Attack { Ranged: true} attack)
+ Entity targetEntity = wallTarget?.Structure ?? SelectedAiTarget?.Entity;
+ IDamageable damageTarget = targetEntity as IDamageable;
+ if (AttackLimb?.attack is Attack { Ranged: true} attack && targetEntity != null)
{
- Limb limb = GetLimbToRotate(attack);
- if (limb != null)
- {
- Vector2 toTarget = damageTarget.WorldPosition - limb.WorldPosition;
- float offset = limb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
- limb.body.SuppressSmoothRotationCalls = false;
- float angle = MathUtils.VectorToAngle(toTarget);
- limb.body.SmoothRotate(angle + offset, attack.AimRotationTorque);
- limb.body.SuppressSmoothRotationCalls = true;
- }
+ AimRangedAttack(attack, targetEntity);
}
if (canAttack)
{
@@ -1908,6 +1899,22 @@ namespace Barotrauma
}
}
+ public void AimRangedAttack(Attack attack, Entity targetEntity)
+ {
+ if (attack == null || attack.Ranged == false || targetEntity == null) { return; }
+ Character.SetInput(InputType.Aim, false, true);
+ Limb limb = GetLimbToRotate(attack);
+ if (limb != null)
+ {
+ Vector2 toTarget = targetEntity.WorldPosition - limb.WorldPosition;
+ float offset = limb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
+ limb.body.SuppressSmoothRotationCalls = false;
+ float angle = MathUtils.VectorToAngle(toTarget);
+ limb.body.SmoothRotate(angle + offset, attack.AimRotationTorque);
+ limb.body.SuppressSmoothRotationCalls = true;
+ }
+ }
+
private bool IsValidAttack(Limb attackingLimb, IEnumerable currentContexts, Entity target)
{
if (attackingLimb == null) { return false; }
@@ -2190,11 +2197,11 @@ namespace Barotrauma
if (!ActiveAttack.IsRunning)
{
#if SERVER
- GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
- AttackLimb,
- damageTarget,
- targetLimb,
- SimPosition));
+ GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
+ AttackLimb,
+ damageTarget,
+ targetLimb,
+ SimPosition));
#else
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
#endif
@@ -3599,7 +3606,10 @@ namespace Barotrauma
{
// We only want to check the visibility when the target is in ruins/wreck/similiar place where sneaking should be possible.
// When the monsters attack the player sub, they wall hack so that they can be more aggressive.
- checkVisibility = target.Entity.Submarine != null && target.Entity.Submarine == Character.Submarine && target.Entity.Submarine.TeamID == CharacterTeamType.None;
+ // Pets should always check the visibility, unless the pet and the target are both outside the submarine -> shouldn't target when they can't perceive (= no wall hack)
+ checkVisibility =
+ Character.IsPet && (Character.Submarine == null) != (target.Entity.Submarine == null) ||
+ target.Entity.Submarine != null && target.Entity.Submarine == Character.Submarine && target.Entity.Submarine.TeamID == CharacterTeamType.None;
}
if (dist > 0)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs
index 3ed35dd0a..a50997a87 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs
@@ -395,6 +395,10 @@ namespace Barotrauma
}
objectiveManager.UpdateObjectives(deltaTime);
+ if (reportProblemsTimer > 0)
+ {
+ reportProblemsTimer -= deltaTime;
+ }
if (reactTimer > 0.0f)
{
reactTimer -= deltaTime;
@@ -407,7 +411,6 @@ namespace Barotrauma
else
{
Character.UpdateTeam();
-
if (Character.CurrentHull != null)
{
if (Character.IsOnPlayerTeam)
@@ -425,19 +428,15 @@ namespace Barotrauma
}
}
}
- if (Character.SpeechImpediment < 100.0f)
+ if (reportProblemsTimer <= 0.0f)
{
- reportProblemsTimer -= deltaTime;
- if (reportProblemsTimer <= 0.0f)
+ if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
{
- if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
- {
- ReportProblems();
- }
- reportProblemsTimer = reportProblemsInterval;
+ ReportProblems();
}
- UpdateSpeaking();
+ reportProblemsTimer = reportProblemsInterval;
}
+ UpdateSpeaking();
UnequipUnnecessaryItems();
reactTimer = GetReactionTime();
}
@@ -912,7 +911,7 @@ namespace Barotrauma
{
Order newOrder = null;
Hull targetHull = null;
- bool speak = true;
+ bool speak = Character.SpeechImpediment < 100;
if (Character.CurrentHull != null)
{
bool isFighting = ObjectiveManager.HasActiveObjective();
@@ -1063,17 +1062,15 @@ namespace Barotrauma
private void UpdateSpeaking()
{
if (!Character.IsOnPlayerTeam) { return; }
-
+ if (Character.SpeechImpediment >= 100) { return; }
if (Character.Oxygen < 20.0f)
{
Character.Speak(TextManager.Get("DialogLowOxygen").Value, null, Rand.Range(0.5f, 5.0f), "lowoxygen".ToIdentifier(), 30.0f);
}
-
if (Character.Bleeding > 2.0f)
{
Character.Speak(TextManager.Get("DialogBleeding").Value, null, Rand.Range(0.5f, 5.0f), "bleeding".ToIdentifier(), 30.0f);
}
-
if (Character.PressureTimer > 50.0f && Character.CurrentHull?.DisplayName != null)
{
Character.Speak(TextManager.GetWithVariable("DialogPressure", "[roomname]", Character.CurrentHull.DisplayName, FormatCapitals.Yes).Value, null, Rand.Range(0.5f, 5.0f), "pressure".ToIdentifier(), 30.0f);
@@ -1860,6 +1857,7 @@ namespace Barotrauma
bool targetAdded = false;
DoForEachCrewMember(caller, humanAI =>
{
+ if (caller != humanAI.Character && caller.SpeechImpediment >= 100) { return; }
var objective = humanAI.ObjectiveManager.GetObjective();
if (objective != null)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs
index 30d687a2a..7af3858b9 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs
@@ -502,10 +502,12 @@ namespace Barotrauma
public static IEnumerable GetTreatableAfflictions(Character character)
{
- foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions())
+ var allAfflictions = character.CharacterHealth.GetAllAfflictions();
+ foreach (Affliction affliction in allAfflictions)
{
if (affliction.Prefab.IsBuff || affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
if (!affliction.Prefab.TreatmentSuitability.Any(kvp => kvp.Value > 0)) { continue; }
+ if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
yield return affliction;
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs
index 99b8a56dc..3a9a063ac 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs
@@ -1836,8 +1836,6 @@ namespace Barotrauma
{
heldItem.FlipX(relativeToSub: false);
}
- // TODO: was this added by a mistake?
- //heldItem.FlipX(relativeToSub: false);
}
foreach (Limb limb in Limbs)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs
index 387472cde..abc470dcd 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
+using Barotrauma.Items.Components;
namespace Barotrauma
{
@@ -530,7 +531,7 @@ namespace Barotrauma
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
- targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
+ effect.AddNearbyTargets(worldPosition, targets);
effect.Apply(effectType, deltaTime, targetEntity, targets);
}
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
@@ -569,7 +570,15 @@ namespace Barotrauma
DamageParticles(deltaTime, worldPosition);
- var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, playSound, targetLimb, penetration: Penetration);
+ float penetration = Penetration;
+
+ float? penetrationValue = SourceItem?.GetComponent()?.Penetration;
+ if (penetrationValue.HasValue)
+ {
+ penetration += penetrationValue.Value;
+ }
+
+ var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, playSound, targetLimb, penetration);
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
foreach (StatusEffect effect in statusEffects)
@@ -599,7 +608,7 @@ namespace Barotrauma
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
- targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
+ effect.AddNearbyTargets(worldPosition, targets);
effect.Apply(effectType, deltaTime, targetLimb.character, targets);
}
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs
index 78d43ee78..caad557b6 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs
@@ -1601,6 +1601,15 @@ namespace Barotrauma
if (Info?.Job == null) { return 0.0f; }
float skillLevel = Info.Job.GetSkillLevel(skillIdentifier);
+ if (overrideStatTypes.TryGetValue(skillIdentifier, out StatTypes statType))
+ {
+ float skillOverride = GetStatValue(statType);
+ if (skillOverride > skillLevel)
+ {
+ skillLevel = skillOverride;
+ }
+ }
+
// apply multipliers first so that multipliers only affect base skill value
foreach (Affliction affliction in CharacterHealth.GetAllAfflictions())
{
@@ -1631,15 +1640,6 @@ namespace Barotrauma
skillLevel += GetStatValue(GetSkillStatType(skillIdentifier));
- if (overrideStatTypes.TryGetValue(skillIdentifier, out StatTypes statType))
- {
- float skillOverride = GetStatValue(statType);
- if (skillOverride > skillLevel)
- {
- skillLevel = skillOverride;
- }
- }
-
return skillLevel;
}
@@ -1972,6 +1972,15 @@ namespace Barotrauma
}
}
#endif
+
+ if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Controlled != this && IsKeyDown(InputType.Aim))
+ {
+ if (currentAttackTarget.AttackLimb?.attack is Attack { Ranged: true } attack && AIController is EnemyAIController enemyAi)
+ {
+ enemyAi.AimRangedAttack(attack, currentAttackTarget.DamageTarget as Entity);
+ }
+ }
+
if (attackCoolDown > 0.0f)
{
attackCoolDown -= deltaTime;
@@ -1982,7 +1991,7 @@ namespace Barotrauma
{
if ((currentAttackTarget.DamageTarget as Entity)?.Removed ?? false)
{
- currentAttackTarget = default(AttackTargetData);
+ currentAttackTarget = default;
}
currentAttackTarget.AttackLimb?.UpdateAttack(deltaTime, currentAttackTarget.AttackPos, currentAttackTarget.DamageTarget, out _);
}
@@ -2077,19 +2086,22 @@ namespace Barotrauma
}
}
- bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected;
- if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
+ if (Inventory != null)
{
- foreach (Item item in HeldItems)
+ bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected;
+ if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
{
- tryUseItem(item, deltaTime);
- }
- foreach (Item item in Inventory.AllItems)
- {
- if (item.GetComponent() is { AllowUseWhenWorn: true } && HasEquippedItem(item))
+ foreach (Item item in HeldItems)
{
tryUseItem(item, deltaTime);
}
+ foreach (Item item in Inventory.AllItems)
+ {
+ if (item.GetComponent() is { AllowUseWhenWorn: true } && HasEquippedItem(item))
+ {
+ tryUseItem(item, deltaTime);
+ }
+ }
}
}
@@ -2170,6 +2182,8 @@ namespace Barotrauma
private AttackTargetData currentAttackTarget;
public void SetAttackTarget(Limb attackLimb, IDamageable damageTarget, Vector2 attackPos)
{
+ DebugConsole.NewMessage($"SetAttackTarget {this.ToString()}: " + (damageTarget?.ToString() ?? null));
+
currentAttackTarget = new AttackTargetData()
{
AttackLimb = attackLimb,
@@ -3824,7 +3838,7 @@ namespace Barotrauma
return attackResult;
}
- public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading, Character attacker = null)
+ public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading, bool ignoreSeveranceProbabilityModifier = false, Character attacker = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
#if DEBUG
@@ -3852,7 +3866,7 @@ namespace Barotrauma
var referenceLimb = targetLimb.type == LimbType.Head && targetLimb.Params.ID == 0 ? joint.LimbA : joint.LimbB;
if (referenceLimb != targetLimb) { continue; }
float probability = severLimbsProbability;
- if (!IsDead && probability < 1)
+ if (!IsDead && !ignoreSeveranceProbabilityModifier)
{
probability *= joint.Params.SeveranceProbabilityModifier;
}
@@ -4111,7 +4125,7 @@ namespace Barotrauma
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
- targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets));
+ statusEffect.AddNearbyTargets(WorldPosition, targets);
statusEffect.Apply(actionType, deltaTime, this, targets);
}
else if (statusEffect.targetLimbs != null)
@@ -4745,6 +4759,8 @@ namespace Barotrauma
public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier;
+ public bool HasJob(Identifier identifier) => Info?.Job?.Prefab.Identifier == identifier;
+
public bool IsProtectedFromPressure()
{
return HasAbilityFlag(AbilityFlags.ImmuneToPressure) || PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f);
@@ -4975,7 +4991,7 @@ namespace Barotrauma
///
private readonly Dictionary wearableStatValues = new Dictionary();
- public float GetStatValue(StatTypes statType)
+ public float GetStatValue(StatTypes statType, bool includeSaved = true)
{
if (!IsHuman) { return 0f; }
@@ -4988,7 +5004,7 @@ namespace Barotrauma
{
statValue += CharacterHealth.GetStatValue(statType);
}
- if (Info != null)
+ if (Info != null && includeSaved)
{
// could be optimized by instead updating the Character.cs statvalues dictionary whenever the CharacterInfo.cs values change
statValue += Info.GetSavedStatValue(statType);
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs
index 393db009a..24a96e3fe 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs
@@ -1233,10 +1233,6 @@ namespace Barotrauma
int prevAmount = ExperiencePoints;
var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f);
- if (isMissionExperience)
- {
- Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier);
- }
experienceGainMultiplier.Value += Character?.GetStatValue(StatTypes.ExperienceGainMultiplier) ?? 0;
amount = (int)(amount * experienceGainMultiplier.Value);
@@ -1808,7 +1804,7 @@ namespace Barotrauma
{
if (SavedStatValues.TryGetValue(statType, out var statValues))
{
- return statValues.Where(s => s.StatIdentifier == statIdentifier).Sum(v => v.StatValue);
+ return statValues.Where(value => ToolBox.StatIdentifierMatches(value.StatIdentifier, statIdentifier)).Sum(static v => v.StatValue);
}
else
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs
index 4aebc88c4..a1a28bd20 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs
@@ -429,7 +429,7 @@ namespace Barotrauma
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
- targets.AddRange(statusEffect.GetNearbyTargets(characterHealth.Character.WorldPosition, targets));
+ statusEffect.AddNearbyTargets(characterHealth.Character.WorldPosition, targets);
statusEffect.Apply(type, deltaTime, characterHealth.Character, targets);
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs
index 91c1998b7..93bbe63d7 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Reflection;
using System.Xml.Linq;
using Barotrauma.Extensions;
+using System.Collections.Immutable;
namespace Barotrauma
{
@@ -214,7 +215,6 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.No)]
public Identifier DialogFlag { get; private set; }
-
[Serialize("", IsPropertySaveable.No)]
public Identifier Tag { get; private set; }
@@ -276,6 +276,47 @@ namespace Barotrauma
}
}
+ public class Description
+ {
+ public enum TargetType
+ {
+ Any,
+ Self,
+ OtherCharacter
+ }
+
+ public readonly LocalizedString Text;
+ public readonly Identifier TextTag;
+ public readonly float MinStrength, MaxStrength;
+ public readonly TargetType Target;
+
+ public Description(ContentXElement element, AfflictionPrefab affliction)
+ {
+ TextTag = element.GetAttributeIdentifier("textidentifier", Identifier.Empty);
+ if (!TextTag.IsEmpty)
+ {
+ Text = TextManager.Get(TextTag);
+ }
+ string text = element.GetAttributeString("text", string.Empty);
+ if (!text.IsNullOrEmpty())
+ {
+ Text = Text?.Fallback(text) ?? text;
+ }
+ else if (TextTag.IsEmpty)
+ {
+ DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - no text defined for one of the descriptions.");
+ }
+
+ MinStrength = element.GetAttributeFloat(nameof(MinStrength), 0.0f);
+ MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
+ if (MinStrength >= MaxStrength)
+ {
+ DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - max strength is not larger than min.");
+ }
+ Target = element.GetAttributeEnum(nameof(Target), TargetType.Any);
+ }
+ }
+
public class PeriodicEffect
{
public readonly List StatusEffects = new List();
@@ -313,7 +354,6 @@ namespace Barotrauma
public static readonly PrefabCollection Prefabs = new PrefabCollection();
- private bool disposed = false;
public override void Dispose() { }
public static IEnumerable List => Prefabs;
@@ -330,15 +370,21 @@ namespace Barotrauma
//(e.g. mental health problems on head, lack of oxygen on torso...)
public readonly LimbType IndicatorLimb;
- public readonly LocalizedString Name, Description;
+ public readonly LocalizedString Name;
public readonly Identifier TranslationIdentifier;
public readonly bool IsBuff;
+ public readonly bool AffectMachines;
public readonly bool HealableInMedicalClinic;
public readonly float HealCostMultiplier;
public readonly int BaseHealCost;
public readonly LocalizedString CauseOfDeathDescription, SelfCauseOfDeathDescription;
+ private readonly LocalizedString defaultDescription;
+ public readonly ImmutableList Descriptions;
+
+ public readonly bool HideIconAfterDelay;
+
//how high the strength has to be for the affliction to take affect
public readonly float ActivationThreshold = 0.0f;
//how high the strength has to be for the affliction icon to be shown in the UI
@@ -355,6 +401,11 @@ namespace Barotrauma
//how strong the affliction needs to be before bots attempt to treat it
public readonly float TreatmentThreshold = 5.0f;
+ ///
+ /// Bots will not try to treat the affliction if the character has any of these afflictions
+ ///
+ public ImmutableHashSet IgnoreTreatmentIfAfflictedBy;
+
///
/// The affliction is automatically removed after this time. 0 = unlimited
///
@@ -413,13 +464,14 @@ namespace Barotrauma
{
Name = Name.Fallback(fallbackName);
}
- Description = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}");
+ defaultDescription = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}");
string fallbackDescription = element.GetAttributeString("description", "");
if (!string.IsNullOrEmpty(fallbackDescription))
{
- Description = Description.Fallback(fallbackDescription);
+ defaultDescription = defaultDescription.Fallback(fallbackDescription);
}
- IsBuff = element.GetAttributeBool("isbuff", false);
+ IsBuff = element.GetAttributeBool(nameof(IsBuff), false);
+ AffectMachines = element.GetAttributeBool(nameof(AffectMachines), true);
HealableInMedicalClinic = element.GetAttributeBool("healableinmedicalclinic",
!IsBuff &&
@@ -428,6 +480,8 @@ namespace Barotrauma
HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier), 1f);
BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost), 0);
+ IgnoreTreatmentIfAfflictedBy = element.GetAttributeIdentifierArray(nameof(IgnoreTreatmentIfAfflictedBy), Array.Empty()).ToImmutableHashSet();
+
Duration = element.GetAttributeFloat(nameof(Duration), 0.0f);
if (element.GetAttribute("nameidentifier") != null)
@@ -445,30 +499,33 @@ namespace Barotrauma
}
}
- ActivationThreshold = element.GetAttributeFloat("activationthreshold", 0.0f);
- ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
- ShowIconToOthersThreshold = element.GetAttributeFloat("showicontoothersthreshold", ShowIconThreshold);
- MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
- GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLowerInvariant(), 0.0f);
+ HideIconAfterDelay = element.GetAttributeBool(nameof(HideIconAfterDelay), false);
- ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold",
+ ActivationThreshold = element.GetAttributeFloat(nameof(ActivationThreshold), 0.0f);
+ ShowIconThreshold = element.GetAttributeFloat(nameof(ShowIconThreshold), Math.Max(ActivationThreshold, 0.05f));
+ ShowIconToOthersThreshold = element.GetAttributeFloat(nameof(ShowIconToOthersThreshold), ShowIconThreshold);
+ MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
+ GrainBurst = element.GetAttributeFloat(nameof(GrainBurst), 0.0f);
+
+ ShowInHealthScannerThreshold = element.GetAttributeFloat(nameof(ShowInHealthScannerThreshold),
Math.Max(ActivationThreshold, AfflictionType == "talentbuff" ? float.MaxValue : ShowIconToOthersThreshold));
- TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f));
+ TreatmentThreshold = element.GetAttributeFloat(nameof(TreatmentThreshold), Math.Max(ActivationThreshold, 5.0f));
- DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
- BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);
+ DamageOverlayAlpha = element.GetAttributeFloat(nameof(DamageOverlayAlpha), 0.0f);
+ BurnOverlayAlpha = element.GetAttributeFloat(nameof(BurnOverlayAlpha), 0.0f);
- KarmaChangeOnApplied = element.GetAttributeFloat("karmachangeonapplied", 0.0f);
+ KarmaChangeOnApplied = element.GetAttributeFloat(nameof(KarmaChangeOnApplied), 0.0f);
CauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeath.{TranslationIdentifier}").Fallback(element.GetAttributeString("causeofdeathdescription", ""));
SelfCauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeathSelf.{TranslationIdentifier}").Fallback(element.GetAttributeString("selfcauseofdeathdescription", ""));
- IconColors = element.GetAttributeColorArray("iconcolors", null);
- AfflictionOverlayAlphaIsLinear = element.GetAttributeBool("afflictionoverlayalphaislinear", false);
- AchievementOnRemoved = element.GetAttributeIdentifier("achievementonremoved", "");
+ IconColors = element.GetAttributeColorArray(nameof(IconColors), null);
+ AfflictionOverlayAlphaIsLinear = element.GetAttributeBool(nameof(AfflictionOverlayAlphaIsLinear), false);
+ AchievementOnRemoved = element.GetAttributeIdentifier(nameof(AchievementOnRemoved), "");
ResetBetweenRounds = element.GetAttributeBool("resetbetweenrounds", false);
+ List descriptions = new List();
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -485,15 +542,38 @@ namespace Barotrauma
case "effect":
case "periodiceffect":
break;
+ case "description":
+ descriptions.Add(new Description(subElement, this));
+ break;
default:
DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})");
break;
}
}
+ Descriptions = descriptions.ToImmutableList();
constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
}
+ public LocalizedString GetDescription(float strength, Description.TargetType targetType)
+ {
+ foreach (var description in Descriptions)
+ {
+ if (strength < description.MinStrength || strength > description.MaxStrength) { continue; }
+ switch (targetType)
+ {
+ case Description.TargetType.Self:
+ if (description.Target == Description.TargetType.OtherCharacter) { continue; }
+ break;
+ case Description.TargetType.OtherCharacter:
+ if (description.Target == Description.TargetType.Self) { continue; }
+ break;
+ }
+ return description.Text;
+ }
+ return defaultDescription;
+ }
+
public static void LoadAllEffects()
{
Prefabs.ForEach(p => p.LoadEffects());
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs
index b7825be29..a4774e7a0 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs
@@ -104,7 +104,7 @@ namespace Barotrauma
public bool DoesBleed
{
- get => Character.Params.Health.DoesBleed;
+ get => Character.Params.Health.DoesBleed && !Character.Params.IsMachine;
private set => Character.Params.Health.DoesBleed = value;
}
@@ -550,7 +550,7 @@ namespace Barotrauma
amount -= reduceAmount;
if (treatmentAction != null)
{
- if (treatmentAction.Value == ActionType.OnUse)
+ if (treatmentAction.Value == ActionType.OnUse || treatmentAction.Value == ActionType.OnSuccess)
{
matchingAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
}
@@ -690,6 +690,7 @@ namespace Barotrauma
private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction, bool allowStacking = true)
{
+ if (Character.Params.IsMachine && !newAffliction.Prefab.AffectMachines) { return; }
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; }
@@ -1076,6 +1077,7 @@ namespace Barotrauma
}
if (strength <= affliction.Prefab.TreatmentThreshold) { continue; }
+ if (afflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Key.Identifier))) { continue; }
if (ignoreHiddenAfflictions)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs
index a71d1e73e..03fb3ad7d 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs
@@ -79,27 +79,35 @@ namespace Barotrauma
public ref readonly ImmutableArray ParsedAfflictionTypes => ref parsedAfflictionTypes;
- public DamageModifier(XElement element, string parentDebugName)
+ public DamageModifier(XElement element, string parentDebugName, bool checkErrors = true)
{
Deserialize(element);
if (element.Attribute("afflictionnames") != null)
{
DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.");
}
- foreach (var afflictionType in parsedAfflictionTypes)
+ if (checkErrors)
{
- if (!AfflictionPrefab.Prefabs.Any(p => p.AfflictionType == afflictionType))
+ foreach (var afflictionType in parsedAfflictionTypes)
{
- createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions of the type \"{afflictionType}\". Did you mean to use an affliction identifier instead?");
- }
- }
- foreach (var afflictionIdentifier in parsedAfflictionIdentifiers)
- {
- if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionIdentifier))
- {
- createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions with the identifier \"{afflictionIdentifier}\". Did you mean to use an affliction type instead?");
+ if (!AfflictionPrefab.Prefabs.Any(p => p.AfflictionType == afflictionType))
+ {
+ createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions of the type \"{afflictionType}\". Did you mean to use an affliction identifier instead?");
+ }
+ }
+ foreach (var afflictionIdentifier in parsedAfflictionIdentifiers)
+ {
+ if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionIdentifier))
+ {
+ createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions with the identifier \"{afflictionIdentifier}\". Did you mean to use an affliction type instead?");
+ }
+ }
+ if (!parsedAfflictionTypes.Any() && !parsedAfflictionIdentifiers.Any())
+ {
+ createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Neither affliction types of identifiers defined.");
}
}
+
static void createWarningOrError(string msg)
{
#if DEBUG
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs
index 03afe476f..b8bc40ecf 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs
@@ -1196,7 +1196,7 @@ namespace Barotrauma
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
- targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets));
+ statusEffect.AddNearbyTargets(WorldPosition, targets);
statusEffect.Apply(actionType, deltaTime, character, targets);
}
else
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs
index b2323c54d..b29d6c2a3 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs
@@ -50,6 +50,9 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.Yes, description: "Can the creature live without water or does it die on dry land?"), Editable]
public bool NeedsWater { get; set; }
+ [Serialize(false, IsPropertySaveable.Yes, description: "Is this creature an artificial creature, like robot or machine that shouldn't be affected by afflictions that affect only organic creatures? Overrides DoesBleed."), Editable]
+ public bool IsMachine { get; set; }
+
[Serialize(false, IsPropertySaveable.No), Editable]
public bool CanSpeak { get; set; }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionLocation.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionLocation.cs
index bfdb89205..c3d2e872e 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionLocation.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionLocation.cs
@@ -27,7 +27,8 @@ namespace Barotrauma.Abilities
{
if (isPositiveReputation)
{
- if (abilityLocation.Location.Reputation.Faction.Reputation.Value <= 0) { return false; }
+ if (abilityLocation.Location?.Reputation is not { } reputation) { return false; }
+ if (reputation.Value <= 0) { return false; }
}
if (locationIdentifiers.Any())
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs
index fd1d55682..4ed81b82f 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs
@@ -33,13 +33,20 @@ namespace Barotrauma.Abilities
{
if (abilityObject is IAbilityMission { Mission: { } mission })
{
- if (isAffiliated && GameMain.GameSession?.Campaign?.Factions.MaxBy(static f => f.Reputation.Value) is { } highestFaction)
+ if (isAffiliated)
{
- if (highestFaction.Reputation.Value < 0 || !mission.ReputationRewards.ContainsKey(highestFaction.Reputation.Identifier))
+ if (GameMain.GameSession?.Campaign?.Factions is not { } factions) { return false; }
+
+ // FIXME there's probably a better way to check the faction affiliated with the mission later
+ foreach (Identifier factionIdentifier in mission.ReputationRewards.Keys)
{
- return false;
+ if (factions.Where(faction => factionIdentifier == faction.Prefab.Identifier).Any(static faction => faction.GetPlayerAffiliationStatus() != FactionAffiliation.Affiliated))
+ {
+ return false;
+ }
}
}
+
return missionType.Contains(mission.Prefab.Type);
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs
index e4458d78b..8b20847b1 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs
@@ -1,9 +1,5 @@
-using System;
-using Barotrauma.Items.Components;
-using System.Collections.Generic;
-using System.Linq;
-using System.Xml.Linq;
-using Barotrauma.Extensions;
+using Barotrauma.Extensions;
+using System;
namespace Barotrauma.Abilities
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionNearbyCharacterCount.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionNearbyCharacterCount.cs
index e8b084f07..5d0acdbf0 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionNearbyCharacterCount.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionNearbyCharacterCount.cs
@@ -24,7 +24,7 @@ internal sealed class AbilityConditionNearbyCharacterCount : AbilityConditionDat
foreach (Character otherCharacter in Character.CharacterList)
{
if (character.Submarine != otherCharacter.Submarine) { continue; }
- if (!IsViableTarget(targetTypes, otherCharacter)) { return false; }
+ if (!IsViableTarget(targetTypes, otherCharacter)) { continue; }
if (Vector2.DistanceSquared(character.WorldPosition, otherCharacter.WorldPosition) < distance * distance)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs
index d408fbc83..969c9c5bd 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs
@@ -50,7 +50,7 @@ namespace Barotrauma.Abilities
else if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
- targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets));
+ statusEffect.AddNearbyTargets(targetCharacter.WorldPosition, targets);
if (!nearbyCharactersAppliesToSelf)
{
targets.RemoveAll(c => c == Character);
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveItemStatToTags.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveItemStatToTags.cs
index 707663745..1b4f880f8 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveItemStatToTags.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveItemStatToTags.cs
@@ -27,6 +27,8 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect()
{
+ if (Character?.Submarine is null) { return; }
+
foreach (Item item in Character.Submarine.GetItems(true))
{
if (item.HasTag(tags) || tags.Contains(item.Prefab.Identifier))
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs
new file mode 100644
index 000000000..130b5b988
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs
@@ -0,0 +1,25 @@
+#nullable enable
+
+namespace Barotrauma.Abilities
+{
+ internal sealed class CharacterAbilityGiveTalentPointsToAllies : CharacterAbility
+ {
+ private readonly int amount;
+
+ public CharacterAbilityGiveTalentPointsToAllies(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
+ {
+ amount = abilityElement.GetAttributeInt("amount", 0);
+ }
+
+ public override void InitializeAbility(bool addingFirstTime)
+ {
+ if (!addingFirstTime) { return; }
+
+ foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
+ {
+ if (character.Info is null) { return; }
+ character.Info.AdditionalTalentPoints += amount;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs
index a6141b79f..686aa6d48 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.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/CharacterAbilitySetMetadataInt.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs
index d24a201f3..a8da25c74 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs
@@ -13,6 +13,11 @@ namespace Barotrauma.Abilities
value = abilityElement.GetAttributeInt("value", 0);
}
+ public override void InitializeAbility(bool addingFirstTime)
+ {
+ ApplyEffect();
+ }
+
protected override void ApplyEffect()
{
if (identifier == Identifier.Empty) { return; }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs
index 9994e51e6..96bdd50a9 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs
@@ -12,6 +12,8 @@ namespace Barotrauma.Abilities
public override void InitializeAbility(bool addingFirstTime)
{
+ if (!addingFirstTime) { return; }
+
JobPrefab? apprentice = CharacterAbilityApplyStatusEffectsToApprenticeship.GetApprenticeJob(Character, JobPrefab.Prefabs.ToImmutableHashSet());
if (apprentice is null)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs
index 18725da29..cf1100db6 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs
@@ -27,39 +27,40 @@ namespace Barotrauma.Abilities
TimeSinceLastUpdate += deltaTime;
if (TimeSinceLastUpdate < interval) { return; }
- bool shouldApplyDelayedEffect;
- bool conditionsDidntMatch;
+ bool conditionsMatched;
if (AllConditionsMatched())
{
effectDelayTimer += TimeSinceLastUpdate;
- shouldApplyDelayedEffect = effectDelayTimer >= effectDelay;
- conditionsDidntMatch = false;
+ bool shouldApplyDelayedEffect = effectDelayTimer >= effectDelay;
+ conditionsMatched = shouldApplyDelayedEffect;
}
else
{
effectDelayTimer = 0f;
- shouldApplyDelayedEffect = false;
- conditionsDidntMatch = true;
+ conditionsMatched = false;
}
bool hasFallbacks = fallbackAbilities.Count > 0;
List abilitiesToRun =
- conditionsDidntMatch && hasFallbacks
+ !conditionsMatched && hasFallbacks
? fallbackAbilities
: characterAbilities;
+ if (hasFallbacks)
+ {
+ conditionsMatched = true;
+ }
+
foreach (var characterAbility in abilitiesToRun)
{
if (!characterAbility.IsViable()) { continue; }
- characterAbility.UpdateCharacterAbility(
- shouldApplyDelayedEffect || conditionsDidntMatch,
- TimeSinceLastUpdate);
+ characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate);
}
- if (shouldApplyDelayedEffect || (conditionsDidntMatch && hasFallbacks))
+ if (conditionsMatched)
{
timesTriggered++;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs
index 396244f9a..2702f649f 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs
@@ -7,7 +7,7 @@ namespace Barotrauma
{
internal sealed class TalentTree : Prefab
{
- public enum TalentTreeStageState
+ public enum TalentStages
{
Invalid,
Locked,
@@ -72,30 +72,30 @@ namespace Barotrauma
// i hate this function - markus
// me too - joonas
- public static TalentTreeStageState GetTalentOptionStageState(Character character, Identifier subTreeIdentifier, int index, IReadOnlyCollection selectedTalents)
+ public static TalentStages GetTalentOptionStageState(Character character, Identifier subTreeIdentifier, int index, IReadOnlyCollection selectedTalents)
{
- if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; }
+ if (character?.Info?.Job.Prefab is null) { return TalentStages.Invalid; }
- if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentTreeStageState.Invalid; }
+ if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentStages.Invalid; }
TalentSubTree subTree = talentTree!.TalentSubTrees.FirstOrDefault(tst => tst.Identifier == subTreeIdentifier);
- if (subTree is null) { return TalentTreeStageState.Invalid; }
+ if (subTree is null) { return TalentStages.Invalid; }
if (!TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents))
{
- return TalentTreeStageState.Locked;
+ return TalentStages.Locked;
}
TalentOption targetTalentOption = subTree.TalentOptionStages[index];
if (targetTalentOption.HasEnoughTalents(character.Info))
{
- return TalentTreeStageState.Unlocked;
+ return TalentStages.Unlocked;
}
if (targetTalentOption.HasSelectedTalent(selectedTalents))
{
- return TalentTreeStageState.Highlighted;
+ return TalentStages.Highlighted;
}
bool hasTalentInLastTier = true;
@@ -111,17 +111,17 @@ namespace Barotrauma
if (!hasTalentInLastTier)
{
- return TalentTreeStageState.Locked;
+ return TalentStages.Locked;
}
bool hasPointsForNewTalent = character.Info.GetTotalTalentPoints() - selectedTalents.Count > 0;
if (hasPointsForNewTalent)
{
- return isLastTalentPurchased ? TalentTreeStageState.Highlighted : TalentTreeStageState.Available;
+ return isLastTalentPurchased ? TalentStages.Highlighted : TalentStages.Available;
}
- return TalentTreeStageState.Locked;
+ return TalentStages.Locked;
}
@@ -207,7 +207,7 @@ namespace Barotrauma
{
nameIdentifier = $"talenttree.{Identifier}";
}
- DisplayName = TextManager.Get($"talenttree.{nameIdentifier}").Fallback(Identifier.Value);
+ DisplayName = TextManager.Get(nameIdentifier).Fallback(Identifier.Value);
Type = subTreeElement.GetAttributeEnum("type", TalentTreeType.Specialization);
RequiredTrees = subTreeElement.GetAttributeIdentifierImmutableHashSet("requires", ImmutableHashSet.Empty);
BlockedTrees = subTreeElement.GetAttributeIdentifierImmutableHashSet("blocks", ImmutableHashSet.Empty);
@@ -234,7 +234,7 @@ namespace Barotrauma
/// When specified the talent option will show talent with this identifier
/// and clicking on it will expand the talent option to show the talents
///
- public readonly Option ShowcaseTalent;
+ public readonly Dictionary> ShowCaseTalents = new Dictionary>();
public bool HasEnoughTalents(CharacterInfo character) => CountMatchingTalents(character.UnlockedTalents) >= MaxChosenTalents;
public bool HasEnoughTalents(IReadOnlyCollection selectedTalents) => CountMatchingTalents(selectedTalents) >= MaxChosenTalents;
@@ -269,19 +269,30 @@ namespace Barotrauma
{
MaxChosenTalents = talentOptionsElement.GetAttributeInt("maxchosentalents", 1);
- Identifier showcaseTalent = talentOptionsElement.GetAttributeIdentifier("showcasetalent", Identifier.Empty);
- ShowcaseTalent = !showcaseTalent.IsEmpty
- ? Option.Some(showcaseTalent)
- : Option.None();
+ HashSet identifiers = new HashSet();
- var talentIdentifiers = new HashSet();
- foreach (var talentOptionElement in talentOptionsElement.GetChildElements("talentoption"))
+ foreach (ContentXElement talentOptionElement in talentOptionsElement.Elements())
{
- Identifier identifier = talentOptionElement.GetAttributeIdentifier("identifier", Identifier.Empty);
- talentIdentifiers.Add(identifier);
+ Identifier elementName = talentOptionElement.Name.ToIdentifier();
+ if (elementName == "talentoption")
+ {
+ identifiers.Add(talentOptionElement.GetAttributeIdentifier("identifier", Identifier.Empty));
+ }
+ else if (elementName == "showcasetalent")
+ {
+ Identifier showCaseIdentifier = talentOptionElement.GetAttributeIdentifier("identifier", Identifier.Empty);
+ HashSet showCaseTalentIdentifiers = new HashSet();
+ foreach (ContentXElement subElement in talentOptionElement.Elements())
+ {
+ Identifier identifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
+ showCaseTalentIdentifiers.Add(identifier);
+ identifiers.Add(identifier);
+ }
+ ShowCaseTalents.Add(showCaseIdentifier, showCaseTalentIdentifiers.ToImmutableHashSet());
+ }
}
- this.talentIdentifiers = talentIdentifiers.ToImmutableHashSet();
+ talentIdentifiers = identifiers.ToImmutableHashSet();
}
}
}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs
index d99d29b57..1e2dccfad 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs
@@ -18,7 +18,7 @@ namespace Barotrauma
public const string LocalModsDir = "LocalMods";
public static readonly string WorkshopModsDir = Barotrauma.IO.Path.Combine(
- SaveUtil.SaveFolder,
+ SaveUtil.DefaultSaveFolder,
"WorkshopMods",
"Installed");
diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/Identifier.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/Identifier.cs
index 824924101..7fb3453ce 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/Identifier.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/Identifier.cs
@@ -125,6 +125,7 @@ namespace Barotrauma
internal int IndexOf(char c) => Value.IndexOf(c);
internal Identifier this[Range range] => Value[range].ToIdentifier();
+ internal Char this[int i] => Value[i];
}
public static class IdentifierExtensions
diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs
index 050230110..7425edd16 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs
@@ -2199,7 +2199,7 @@ namespace Barotrauma
//Dont do a thing, random is basically Human points anyways - its in the help description.
break;
default:
- var matchingCharacter = FindMatchingCharacter(args.Skip(1).ToArray());
+ var matchingCharacter = FindMatchingCharacter(args.Skip(1).Take(1).ToArray());
if (matchingCharacter != null){ spawnInventory = matchingCharacter.Inventory; }
break;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs
index f23d4a02c..2eafaf60e 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs
@@ -154,6 +154,7 @@ namespace Barotrauma
BallastFloraDamageMultiplier,
HoldBreathMultiplier,
Apprenticeship,
+ Affiliation,
CPRBoost
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs
index 9413193d1..4269be922 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs
@@ -25,6 +25,9 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.Yes)]
public bool RequireEquipped { get; set; }
+ [Serialize(true, IsPropertySaveable.Yes)]
+ public bool Recursive { get; set; }
+
[Serialize(-1, IsPropertySaveable.Yes)]
public int ItemContainerIndex { get; set; }
@@ -97,7 +100,7 @@ namespace Barotrauma
{
if (inventory == null) { return false; }
int count = 0;
- foreach (Item item in inventory.FindAllItems(it => itemTags.Any(it.HasTag) || itemIdentifierSplit.Contains(it.Prefab.Identifier)))
+ foreach (Item item in inventory.FindAllItems(it => itemTags.Any(it.HasTag) || itemIdentifierSplit.Contains(it.Prefab.Identifier), recursive: Recursive))
{
if (!ConditionalsMatch(item, character)) { continue; }
count++;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckMissionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckMissionAction.cs
new file mode 100644
index 000000000..530a63429
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckMissionAction.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Linq;
+
+namespace Barotrauma;
+
+class CheckMissionAction : BinaryOptionAction
+{
+ public enum MissionType
+ {
+ Current,
+ Selected,
+ Available
+ }
+
+ [Serialize(MissionType.Current, IsPropertySaveable.Yes)]
+ public MissionType Type { get; set; }
+
+ [Serialize("", IsPropertySaveable.Yes)]
+ public Identifier MissionIdentifier { get; set; }
+
+ [Serialize("", IsPropertySaveable.Yes)]
+ public Identifier MissionTag { get; set; }
+
+ [Serialize(1, IsPropertySaveable.Yes)]
+ public int MissionCount { get; set; }
+
+ public CheckMissionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
+ {
+ MissionCount = Math.Max(MissionCount, 0);
+ }
+
+ protected override bool? DetermineSuccess()
+ {
+ var missions = Type switch
+ {
+ MissionType.Current => GameMain.GameSession?.Missions,
+ MissionType.Selected => GameMain.GameSession?.Campaign?.Missions,
+ MissionType.Available => GameMain.GameSession?.Map?.CurrentLocation?.AvailableMissions,
+ _ => null
+ };
+ if (missions is not null)
+ {
+ if (!MissionIdentifier.IsEmpty)
+ {
+ return missions.Any(m => m.Prefab.Identifier == MissionIdentifier);
+ }
+ else if (!MissionTag.IsEmpty)
+ {
+ return missions.Count(m => m.Prefab.Tags.Contains(MissionTag.Value)) >= MissionCount;
+ }
+ else
+ {
+ return missions.Count() >= MissionCount;
+ }
+ }
+ return MissionIdentifier.IsEmpty && MissionTag.IsEmpty && MissionCount == 0;
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckObjectiveAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckObjectiveAction.cs
new file mode 100644
index 000000000..15e5e90a9
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckObjectiveAction.cs
@@ -0,0 +1,15 @@
+namespace Barotrauma;
+
+partial class CheckObjectiveAction : BinaryOptionAction
+{
+ public CheckObjectiveAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
+
+ protected override bool? DetermineSuccess()
+ {
+ bool success = false;
+ DetermineSuccessProjSpecific(ref success);
+ return success;
+ }
+
+ partial void DetermineSuccessProjSpecific(ref bool success);
+}
\ 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 6723d3abb..bb8b8e44d 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs
@@ -1,7 +1,15 @@
+using Barotrauma.Extensions;
+
namespace Barotrauma
{
class CheckOrderAction : BinaryOptionAction
{
+ public enum OrderPriority
+ {
+ Top,
+ Any
+ }
+
[Serialize("", IsPropertySaveable.Yes)]
public Identifier TargetTag { get; set; }
@@ -14,35 +22,58 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes)]
public Identifier OrderTargetTag { get; set; }
+ [Serialize(OrderPriority.Top, IsPropertySaveable.Yes)]
+ public OrderPriority Priority { get; set; }
+
public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
protected override bool? DetermineSuccess()
{
- Character targetCharacter = null;
- if (!TargetTag.IsEmpty)
+ var targetCharacters = ParentEvent.GetTargets(TargetTag);
+ if (targetCharacters.None())
{
- foreach (var t in ParentEvent.GetTargets(TargetTag))
- {
- if (t is Character c)
- {
- targetCharacter = c;
- break;
- }
- }
- }
- if (targetCharacter == null)
- {
- DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target character was found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
+ DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target characters were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
return false;
}
- var currentOrderInfo = targetCharacter.GetCurrentOrderWithTopPriority();
- if (currentOrderInfo?.Identifier == OrderIdentifier)
+ foreach (var t in targetCharacters)
{
- if (!OrderTargetTag.IsEmpty)
+ if (t is not Character c)
{
- if (currentOrderInfo.TargetEntity is not Item targetItem || !targetItem.HasTag(OrderTargetTag)) { return false; }
+ continue;
+ }
+ if (Priority == OrderPriority.Top)
+ {
+ if (c.GetCurrentOrderWithTopPriority() is Order topPrioOrder && IsMatch(topPrioOrder))
+ {
+ return true;
+ }
+ }
+ else if (Priority == OrderPriority.Any)
+ {
+ foreach (var order in c.CurrentOrders)
+ {
+ if (IsMatch(order))
+ {
+ return true;
+ }
+ }
+ }
+
+ bool IsMatch(Order order)
+ {
+ if (order?.Identifier == OrderIdentifier)
+ {
+ if (!OrderTargetTag.IsEmpty && (order.TargetEntity is not Item targetItem || !targetItem.HasTag(OrderTargetTag)))
+ {
+ return false;
+ }
+ if (OrderOption.IsEmpty || order?.Option == OrderOption)
+ {
+ return true;
+ }
+ }
+ return false;
}
- return OrderOption.IsEmpty || currentOrderInfo?.Option == OrderOption;
}
return false;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckPurchasedItemsAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckPurchasedItemsAction.cs
new file mode 100644
index 000000000..b0ac35616
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckPurchasedItemsAction.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Linq;
+
+namespace Barotrauma;
+
+class CheckPurchasedItemsAction : BinaryOptionAction
+{
+ public enum TransactionType
+ {
+ Purchased,
+ Sold
+ }
+
+ [Serialize(TransactionType.Purchased, IsPropertySaveable.Yes)]
+ public TransactionType Type { get; set; }
+
+ [Serialize("", IsPropertySaveable.Yes)]
+ public Identifier ItemIdentifier { get; set; }
+
+ [Serialize("", IsPropertySaveable.Yes)]
+ public Identifier ItemTag { get; set; }
+
+ [Serialize(1, IsPropertySaveable.Yes)]
+ public int MinCount { get; set; }
+
+ public CheckPurchasedItemsAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
+ {
+ MinCount = Math.Max(MinCount, 1);
+ }
+
+ protected override bool? DetermineSuccess()
+ {
+ if (ItemIdentifier.IsEmpty && ItemTag.IsEmpty)
+ {
+ return false;
+ }
+ if (GameMain.GameSession?.Campaign?.CargoManager is not CargoManager cargoManager)
+ {
+ return false;
+ }
+ if (Type == TransactionType.Purchased)
+ {
+ int totalPurchased = 0;
+ foreach ((Identifier id, var items) in cargoManager.PurchasedItems)
+ {
+ if (!ItemIdentifier.IsEmpty)
+ {
+ totalPurchased += items.Find(i => i.ItemPrefabIdentifier == ItemIdentifier)?.Quantity ?? 0;
+ }
+ else if (!ItemTag.IsEmpty)
+ {
+ foreach (var item in items)
+ {
+ if (item.ItemPrefab.Tags.Contains(ItemTag))
+ {
+ totalPurchased += item.Quantity;
+ }
+ }
+ }
+ if (totalPurchased >= MinCount)
+ {
+ return true;
+ }
+ }
+ }
+ else
+ {
+ int totalSold = 0;
+ foreach ((Identifier id, var items) in cargoManager.SoldItems)
+ {
+ if (!ItemIdentifier.IsEmpty)
+ {
+ totalSold += items.Count(i => i.ItemPrefab.Identifier == ItemIdentifier);
+ }
+ else if (!ItemTag.IsEmpty)
+ {
+ totalSold += items.Count(i => i.ItemPrefab.Tags.Contains(ItemTag));
+ }
+ if (totalSold >= MinCount)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs
index a438db9a5..1c5c8f0b5 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs
@@ -59,7 +59,10 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.Yes)]
public bool ContinueConversation { get; set; }
- public Character speaker
+ [Serialize(false, IsPropertySaveable.Yes)]
+ public bool IgnoreInterruptDistance { get; set; }
+
+ public Character Speaker
{
get;
private set;
@@ -124,7 +127,7 @@ namespace Barotrauma
#else
foreach (Client c in GameMain.Server.ConnectedClients)
{
- if (c.InGame && c.Character != null) { ServerWrite(speaker, c, interrupt); }
+ if (c.InGame && c.Character != null) { ServerWrite(Speaker, c, interrupt); }
}
#endif
ResetSpeaker();
@@ -160,7 +163,7 @@ namespace Barotrauma
selectedOption = -1;
interrupt = false;
dialogOpened = false;
- speaker = null;
+ Speaker = null;
}
public override bool SetGoToTarget(string goTo)
@@ -181,15 +184,14 @@ namespace Barotrauma
private void ResetSpeaker()
{
- if (speaker == null) { return; }
- speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
- speaker.ActiveConversation = null;
- speaker.SetCustomInteract(null, null);
+ if (Speaker == null) { return; }
+ Speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
+ Speaker.ActiveConversation = null;
+ Speaker.SetCustomInteract(null, null);
#if SERVER
- GameMain.NetworkMember.CreateEntityEvent(speaker, new Character.AssignCampaignInteractionEventData());
+ GameMain.NetworkMember.CreateEntityEvent(Speaker, new Character.AssignCampaignInteractionEventData());
#endif
- var humanAI = speaker.AIController as HumanAIController;
- if (humanAI != null && !speaker.IsDead && !speaker.Removed)
+ if (Speaker.AIController is HumanAIController humanAI && !Speaker.IsDead && !Speaker.Removed)
{
humanAI.ClearForcedOrder();
if (prevIdleObjective != null) { humanAI.ObjectiveManager.AddObjective(prevIdleObjective); }
@@ -207,7 +209,6 @@ namespace Barotrauma
public override void Update(float deltaTime)
{
- lastActiveTime = Timing.TotalTime;
if (interrupt)
{
Interrupted?.Update(deltaTime);
@@ -216,6 +217,7 @@ namespace Barotrauma
{
if (dialogOpened)
{
+ lastActiveTime = Timing.TotalTime;
#if CLIENT
if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "ConversationAction"))
{
@@ -226,7 +228,7 @@ namespace Barotrauma
Reset();
}
#endif
- if (ShouldInterrupt())
+ if (ShouldInterrupt(requireTarget: true))
{
ResetSpeaker();
interrupt = true;
@@ -236,34 +238,34 @@ namespace Barotrauma
if (!SpeakerTag.IsEmpty)
{
- if (speaker != null && !speaker.Removed && speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && speaker.ActiveConversation?.ParentEvent != this.ParentEvent) { return; }
- speaker = ParentEvent.GetTargets(SpeakerTag).FirstOrDefault(e => e is Character) as Character;
- if (speaker == null || speaker.Removed)
+ if (Speaker != null && !Speaker.Removed && Speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && Speaker.ActiveConversation?.ParentEvent != this.ParentEvent) { return; }
+ Speaker = ParentEvent.GetTargets(SpeakerTag).FirstOrDefault(e => e is Character) as Character;
+ if (Speaker == null || Speaker.Removed)
{
return;
}
//some conversation already assigned to the speaker, wait for it to be removed
- if (speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && speaker.ActiveConversation?.ParentEvent != this.ParentEvent)
+ if (Speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && Speaker.ActiveConversation?.ParentEvent != this.ParentEvent)
{
return;
}
else if (!WaitForInteraction)
{
- TryStartConversation(speaker);
+ TryStartConversation(Speaker);
}
- else if (speaker.ActiveConversation != this)
+ else if (Speaker.ActiveConversation != this)
{
- speaker.CampaignInteractionType = CampaignMode.InteractionType.Talk;
- speaker.ActiveConversation = this;
+ Speaker.CampaignInteractionType = CampaignMode.InteractionType.Talk;
+ Speaker.ActiveConversation = this;
#if CLIENT
- speaker.SetCustomInteract(
+ Speaker.SetCustomInteract(
TryStartConversation,
TextManager.GetWithVariable("CampaignInteraction.Talk", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)));
#else
- speaker.SetCustomInteract(
+ Speaker.SetCustomInteract(
TryStartConversation,
TextManager.Get("CampaignInteraction.Talk"));
- GameMain.NetworkMember.CreateEntityEvent(speaker, new Character.AssignCampaignInteractionEventData());
+ GameMain.NetworkMember.CreateEntityEvent(Speaker, new Character.AssignCampaignInteractionEventData());
#endif
}
return;
@@ -275,7 +277,9 @@ namespace Barotrauma
}
else
{
- if (ShouldInterrupt())
+ //after the conversation has been finished and the target character assigned,
+ //we no longer care if we still have a target
+ if (ShouldInterrupt(requireTarget: false))
{
ResetSpeaker();
interrupt = true;
@@ -287,35 +291,36 @@ namespace Barotrauma
}
}
- private bool ShouldInterrupt()
+ private bool ShouldInterrupt(bool requireTarget)
{
IEnumerable targets = Enumerable.Empty();
- if (!TargetTag.IsEmpty)
+ if (!TargetTag.IsEmpty && requireTarget)
{
- targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e));
+ targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e, requireTarget));
if (!targets.Any()) { return true; }
}
- if (speaker != null)
+ if (Speaker != null)
{
- if (!TargetTag.IsEmpty)
+ if (!TargetTag.IsEmpty && requireTarget && !IgnoreInterruptDistance)
{
- if (targets.All(t => Vector2.DistanceSquared(t.WorldPosition, speaker.WorldPosition) > InterruptDistance * InterruptDistance)) { return true; }
+ if (targets.All(t => Vector2.DistanceSquared(t.WorldPosition, Speaker.WorldPosition) > InterruptDistance * InterruptDistance)) { return true; }
}
- if (speaker.AIController is HumanAIController humanAI && !humanAI.AllowCampaignInteraction())
+ if (Speaker.AIController is HumanAIController humanAI && !humanAI.AllowCampaignInteraction())
{
return true;
}
- return speaker.Removed || speaker.IsDead || speaker.IsIncapacitated;
+ return Speaker.Removed || Speaker.IsDead || Speaker.IsIncapacitated;
}
return false;
}
- private bool IsValidTarget(Entity e)
+ private bool IsValidTarget(Entity e, bool requirePlayerControlled = true)
{
- bool isValid = e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
- (e == Character.Controlled || character.IsRemotePlayer);
+ bool isValid =
+ e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
+ (character == Character.Controlled || character.IsRemotePlayer || !requirePlayerControlled);
#if SERVER
if (!dialogOpened)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs
index 2834b8b84..6ac082e66 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs
@@ -49,6 +49,12 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes)]
public Identifier ObjectiveTag { get; set; }
+ [Serialize(true, IsPropertySaveable.Yes)]
+ public bool ObjectiveCanBeCompleted { get; set; }
+
+ [Serialize("", IsPropertySaveable.Yes)]
+ public Identifier ParentObjectiveId { get; set; }
+
private bool isFinished = false;
public MessageBoxAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs
index 1db3cdd56..ee26b9283 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs
@@ -67,7 +67,7 @@ namespace Barotrauma
void ChangeItemTeam(Submarine sub, bool allowStealing)
{
- foreach (Item item in npc.Inventory.AllItems)
+ foreach (Item item in npc.Inventory.FindAllItems(recursive: true))
{
item.AllowStealing = allowStealing;
if (item.GetComponent() is { } wifiComponent)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs
index 77661bdbc..ea2851339 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs
@@ -1,9 +1,8 @@
-using System;
-using Barotrauma.Extensions;
+using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
+using System;
using System.Collections.Generic;
using System.Linq;
-using System.Xml.Linq;
namespace Barotrauma
{
@@ -11,6 +10,7 @@ namespace Barotrauma
{
public enum SpawnLocationType
{
+ Any,
MainSub,
Outpost,
MainPath,
@@ -40,7 +40,7 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes, description: "Tag of an entity with an inventory to spawn the item into.")]
public Identifier TargetInventory { get; set; }
- [Serialize(SpawnLocationType.MainSub, IsPropertySaveable.Yes)]
+ [Serialize(SpawnLocationType.Any, IsPropertySaveable.Yes)]
public SpawnLocationType SpawnLocation { get; set; }
[Serialize(SpawnType.Human, IsPropertySaveable.Yes)]
@@ -177,7 +177,7 @@ namespace Barotrauma
}
else if (!ItemIdentifier.IsEmpty)
{
- if (!(MapEntityPrefab.FindByIdentifier(ItemIdentifier) is ItemPrefab itemPrefab))
+ if (MapEntityPrefab.FindByIdentifier(ItemIdentifier) is not ItemPrefab itemPrefab)
{
DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)");
}
@@ -275,6 +275,7 @@ namespace Barotrauma
{
return spawnLocation switch
{
+ SpawnLocationType.Any => true,
SpawnLocationType.MainSub => submarine == Submarine.MainSub,
SpawnLocationType.MainPath => submarine == null,
SpawnLocationType.Outpost => submarine is { Info: { IsOutpost: true } },
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs
index 208a7fbfa..89eff4f39 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs
@@ -32,11 +32,13 @@ namespace Barotrauma
("bot", v => TagBots(playerCrewOnly: false)),
("crew", v => TagCrew()),
("humanprefabidentifier", TagHumansByIdentifier),
+ ("jobidentifier", TagHumansByJobIdentifier),
("structureidentifier", TagStructuresByIdentifier),
("structurespecialtag", TagStructuresBySpecialTag),
("itemidentifier", TagItemsByIdentifier),
("itemtag", TagItemsByTag),
- ("hullname", TagHullsByName)
+ ("hullname", TagHullsByName),
+ ("submarine", TagSubmarinesByType),
}.Select(t => (t.k.ToIdentifier(), t.v)).ToImmutableDictionary();
}
@@ -93,6 +95,18 @@ namespace Barotrauma
}
}
}
+
+ private void TagHumansByJobIdentifier(Identifier jobIdentifier)
+ {
+ foreach (Character c in Character.CharacterList)
+ {
+ if (c.HasJob(jobIdentifier))
+ {
+ ParentEvent.AddTarget(Tag, c);
+ }
+ }
+ }
+
private void TagStructuresByIdentifier(Identifier identifier)
{
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier == identifier);
@@ -118,6 +132,11 @@ namespace Barotrauma
ParentEvent.AddTargetPredicate(Tag, e => e is Hull h && SubmarineTypeMatches(h.Submarine) && h.RoomName.Contains(name.Value, StringComparison.OrdinalIgnoreCase));
}
+ private void TagSubmarinesByType(Identifier type)
+ {
+ ParentEvent.AddTargetPredicate(Tag, e => e is Submarine s && SubmarineTypeMatches(s) && (type.IsEmpty || type == s.Info?.Type.ToIdentifier()));
+ }
+
private bool SubmarineTypeMatches(Submarine sub)
{
if (SubmarineType == SubType.Any) { return true; }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs
index 385dc13ae..8f7a849ac 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs
@@ -8,6 +8,12 @@ namespace Barotrauma
{
class TriggerAction : EventAction
{
+ public enum TriggerType
+ {
+ Inside,
+ Outside
+ }
+
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the first entity that will be used for trigger checks.")]
public Identifier Target1Tag { get; set; }
@@ -23,7 +29,10 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the second entity when the trigger check succeeds.")]
public Identifier ApplyToTarget2 { get; set; }
- [Serialize(0.0f, IsPropertySaveable.Yes, description: "Range both entities must be within to activate the trigger.")]
+ [Serialize(TriggerType.Inside, IsPropertySaveable.Yes, description: "Determines if the targets must be inside or outside of the radius.")]
+ public TriggerType Type { get; set; }
+
+ [Serialize(0.0f, IsPropertySaveable.Yes, description: "Range to activate the trigger.")]
public float Radius { get; set; }
[Serialize(true, IsPropertySaveable.Yes, description: "If true, characters who are being targeted by some enemy cannot trigger the action.")]
@@ -38,6 +47,9 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.Yes, description: "If true, the action can be triggered by interacting with any matching target (not just the 1st one).")]
public bool AllowMultipleTargets { get; set; }
+ [Serialize(false, IsPropertySaveable.Yes, description: "If true and using multiple targets, all targets must be inside/outside the radius.")]
+ public bool CheckAllTargets { get; set; }
+
private float distance;
public TriggerAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -57,6 +69,8 @@ namespace Barotrauma
public bool isRunning = false;
private readonly List> npcsOrItems = new List>();
+
+ private readonly List<(Entity e1, Entity e2)> triggerers = new List<(Entity e1, Entity e2)>();
public override void Update(float deltaTime)
{
@@ -66,18 +80,44 @@ namespace Barotrauma
var targets1 = ParentEvent.GetTargets(Target1Tag);
if (!targets1.Any()) { return; }
-
+
+ triggerers.Clear();
foreach (Entity e1 in targets1)
{
- if (DisableInCombat && IsInCombat(e1)) { continue; }
- if (DisableIfTargetIncapacitated && e1 is Character character1 && (character1.IsDead || character1.IsIncapacitated)) { continue; }
+ if (DisableInCombat && IsInCombat(e1))
+ {
+ if (CheckAllTargets)
+ {
+ return;
+ }
+ continue;
+ }
+ if (DisableIfTargetIncapacitated && e1 is Character character1 && (character1.IsDead || character1.IsIncapacitated))
+ {
+ if (CheckAllTargets)
+ {
+ return;
+ }
+ continue;
+ }
if (!TargetModuleType.IsEmpty)
{
- if (IsCloseEnoughToHull(e1, out Hull hull))
+ if (!CheckAllTargets && CheckDistanceToHull(e1, out Hull hull))
{
Trigger(e1, hull);
return;
}
+ else if (CheckAllTargets)
+ {
+ if (CheckDistanceToHull(e1, out hull))
+ {
+ triggerers.Add((e1, hull));
+ }
+ else
+ {
+ return;
+ }
+ }
continue;
}
@@ -85,9 +125,26 @@ namespace Barotrauma
foreach (Entity e2 in targets2)
{
- if (e1 == e2) { continue; }
- if (DisableInCombat && IsInCombat(e2)) { continue; }
- if (DisableIfTargetIncapacitated && e2 is Character character2 && (character2.IsDead || character2.IsIncapacitated)) { continue; }
+ if (e1 == e2)
+ {
+ continue;
+ }
+ if (DisableInCombat && IsInCombat(e2))
+ {
+ if (CheckAllTargets)
+ {
+ return;
+ }
+ continue;
+ }
+ if (DisableIfTargetIncapacitated && e2 is Character character2 && (character2.IsDead || character2.IsIncapacitated))
+ {
+ if (CheckAllTargets)
+ {
+ return;
+ }
+ continue;
+ }
if (WaitForInteraction)
{
@@ -173,16 +230,35 @@ namespace Barotrauma
Vector2 pos1 = e1.WorldPosition;
Vector2 pos2 = e2.WorldPosition;
distance = Vector2.Distance(pos1, pos2);
- if (((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
- ((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
- Vector2.DistanceSquared(pos1, pos2) < Radius * Radius)
+ if ((Type == TriggerType.Inside) == IsWithinRadius())
+ {
+ if (!CheckAllTargets)
+ {
+ Trigger(e1, e2);
+ return;
+ }
+ else
+ {
+ triggerers.Add((e1, e2));
+ }
+ }
+ else if (CheckAllTargets)
{
- Trigger(e1, e2);
return;
}
+
+ bool IsWithinRadius() =>
+ ((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
+ ((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
+ Vector2.DistanceSquared(pos1, pos2) < Radius * Radius;
}
}
- }
+ }
+
+ foreach (var (e1, e2) in triggerers)
+ {
+ Trigger(e1, e2);
+ }
}
private void ResetTargetIcons()
@@ -205,7 +281,7 @@ namespace Barotrauma
}
}
- private bool IsCloseEnoughToHull(Entity e, out Hull hull)
+ private bool CheckDistanceToHull(Entity e, out Hull hull)
{
hull = null;
if (Radius <= 0)
@@ -213,36 +289,35 @@ namespace Barotrauma
if (e is Character character && character.CurrentHull != null && character.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
{
hull = character.CurrentHull;
- return true;
+ return Type == TriggerType.Inside;
}
else if (e is Item item && item.CurrentHull != null && item.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
{
hull = item.CurrentHull;
- return true;
+ return Type == TriggerType.Inside;
}
- return false;
+ return Type == TriggerType.Outside;
}
else
{
foreach (Hull potentialHull in Hull.HullList)
{
if (!potentialHull.OutpostModuleTags.Contains(TargetModuleType)) { continue; }
-
Rectangle hullRect = potentialHull.WorldRect;
hullRect.Inflate(Radius, Radius);
if (Submarine.RectContains(hullRect, e.WorldPosition))
{
hull = potentialHull;
- return true;
+ return Type == TriggerType.Inside;
}
}
- return false;
+ return Type == TriggerType.Outside;
}
}
- private bool IsInCombat(Entity entity)
+ private static bool IsInCombat(Entity entity)
{
- if (!(entity is Character character)) { return false; }
+ if (entity is not Character character) { return false; }
foreach (Character c in Character.CharacterList)
{
if (c.IsDead || c.Removed || c.IsIncapacitated || !c.Enabled) { continue; }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs
index 587fb20a7..f2128f82e 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs
@@ -13,6 +13,12 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes)]
public Identifier ObjectiveTag { get; set; }
+ [Serialize(true, IsPropertySaveable.Yes)]
+ public bool CanBeCompleted { get; set; }
+
+ [Serialize("", IsPropertySaveable.Yes)]
+ public Identifier ParentObjectiveId { get; set; }
+
[Serialize(false, IsPropertySaveable.Yes)]
public bool AutoPlayVideo { get; set; }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs
index 8ecedce89..481a4a40a 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs
@@ -73,6 +73,7 @@ namespace Barotrauma
private readonly HashSet finishedEvents = new HashSet();
private readonly HashSet nonRepeatableEvents = new HashSet();
+ private readonly HashSet usedUniqueSets = new HashSet();
#if DEBUG && SERVER
@@ -155,12 +156,19 @@ namespace Barotrauma
}
rand = new MTRandom(seed);
- EventSet initialEventSet = SelectRandomEvents(EventSet.Prefabs.ToList(), requireCampaignSet: GameMain.GameSession?.GameMode is CampaignMode, rand);
+ bool playingCampaign = GameMain.GameSession?.GameMode is CampaignMode;
+ EventSet initialEventSet = SelectRandomEvents(
+ EventSet.Prefabs.ToList(),
+ requireCampaignSet: playingCampaign,
+ random: rand);
EventSet additiveSet = null;
if (initialEventSet != null && initialEventSet.Additive)
{
additiveSet = initialEventSet;
- initialEventSet = SelectRandomEvents(EventSet.Prefabs.Where(e => !e.Additive).ToList(), requireCampaignSet: GameMain.GameSession?.GameMode is CampaignMode, rand);
+ initialEventSet = SelectRandomEvents(
+ EventSet.Prefabs.Where(e => !e.Additive).ToList(),
+ requireCampaignSet: playingCampaign,
+ random: rand);
}
if (initialEventSet != null)
{
@@ -201,6 +209,7 @@ namespace Barotrauma
}
AddChildEvents(initialEventSet);
+
void AddChildEvents(EventSet eventSet)
{
if (eventSet == null) { return; }
@@ -351,6 +360,7 @@ namespace Barotrauma
QueuedEvents.Clear();
finishedEvents.Clear();
nonRepeatableEvents.Clear();
+ usedUniqueSets.Clear();
preloadedSprites.ForEach(s => s.Remove());
preloadedSprites.Clear();
@@ -364,15 +374,25 @@ namespace Barotrauma
///
public void RegisterEventHistory()
{
- level.LevelData.EventsExhausted = true;
- if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost)
+ if (level?.LevelData != null)
{
- 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.EventsExhausted = true;
+ if (level.LevelData.Type == LevelData.LevelType.Outpost)
{
- level.LevelData.EventHistory.RemoveRange(0, 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.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
}
}
@@ -398,6 +418,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)
@@ -496,12 +521,12 @@ namespace Barotrauma
selectedEvents[eventSet].Add(newEvent);
}
- Location location = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation ?? level?.StartLocation;
+ var location = GetEventLocation();
foreach (EventSet childEventSet in eventSet.ChildSets)
{
if (!IsValidForLevel(childEventSet, level)) { continue; }
- if (location != null && !IsValidForLocation(childEventSet, location)) { continue; }
- CreateEvents(childEventSet);
+ if (!IsValidForLocation(childEventSet, location)) { continue; }
+ CreateEvents(childEventSet);
}
}
}
@@ -536,10 +561,32 @@ namespace Barotrauma
}
}
- Location location = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation ?? level?.StartLocation;
- if (location != null)
+ var location = GetEventLocation();
+ allowedEventSets = allowedEventSets.Where(set => IsValidForLocation(set, location));
+
+ allowedEventSets = allowedEventSets.Where(set => !set.CampaignTutorialOnly ||
+ (GameMain.IsSingleplayer && GameMain.GameSession?.Campaign?.Settings is { TutorialEnabled: true }));
+
+ int? discoveryIndex = GameMain.GameSession?.Map?.GetDiscoveryIndex(location);
+ int? visitIndex = GameMain.GameSession?.Map?.GetVisitIndex(location);
+ if (discoveryIndex is not null && discoveryIndex >= 0 && allowedEventSets.Any(set => set.ForceAtDiscoveredNr == discoveryIndex))
{
- allowedEventSets = allowedEventSets.Where(set => IsValidForLocation(set, location));
+ allowedEventSets = allowedEventSets.Where(set => set.ForceAtDiscoveredNr == discoveryIndex);
+ }
+ else if (visitIndex is not null && visitIndex >= 0 && allowedEventSets.Any(set => set.ForceAtVisitedNr == visitIndex))
+ {
+ allowedEventSets = allowedEventSets.Where(set => set.ForceAtVisitedNr == visitIndex);
+ }
+ else
+ {
+ // When there are no forced sets, only allow sets that aren't forced at any specific location
+ allowedEventSets = allowedEventSets.Where(set => set.ForceAtDiscoveredNr < 0 && set.ForceAtVisitedNr < 0);
+ }
+
+ if (allowedEventSets.Count() == 1)
+ {
+ // When there's only a single set available, just select it directly
+ return allowedEventSets.First();
}
float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
@@ -558,18 +605,27 @@ namespace Barotrauma
return null;
}
- private bool IsValidForLevel(EventSet eventSet, Level level)
+ private static bool IsValidForLevel(EventSet eventSet, Level level)
{
return
level.Difficulty >= eventSet.MinLevelDifficulty && level.Difficulty <= eventSet.MaxLevelDifficulty &&
level.LevelData.Type == eventSet.LevelType &&
- (eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier);
+ (eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier) &&
+ (!eventSet.Unique || !level.LevelData.UsedUniqueSets.Contains(eventSet.Identifier));
}
private bool IsValidForLocation(EventSet eventSet, Location location)
{
- return eventSet.LocationTypeIdentifiers == null ||
- eventSet.LocationTypeIdentifiers.Any(identifier => identifier == location.GetLocationType().Identifier);
+ if (location is null) { return true; }
+ var locationType = location.GetLocationType();
+ bool includeGenericEvents = level.Type == LevelData.LevelType.LocationConnection || !locationType.IgnoreGenericEvents;
+ if (includeGenericEvents && eventSet.LocationTypeIdentifiers == null) { return true; }
+ return eventSet.LocationTypeIdentifiers != null && eventSet.LocationTypeIdentifiers.Any(identifier => identifier == locationType.Identifier);
+ }
+
+ private Location GetEventLocation()
+ {
+ return GameMain.GameSession?.Campaign?.Map?.CurrentLocation ?? level?.StartLocation;
}
private bool CanStartEventSet(EventSet eventSet)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs
index cd6a3cae6..bb8bbd1aa 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs
@@ -113,8 +113,17 @@ namespace Barotrauma
public readonly bool PerRuin, PerCave, PerWreck;
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.
+ ///
public readonly bool OncePerOutpost;
+ ///
+ /// If true, the whole set can only be selected once for a level.
+ ///
+ public readonly bool Unique;
+
public readonly bool DelayWhenCrewAway;
public readonly bool TriggerEventCooldown;
@@ -126,6 +135,18 @@ namespace Barotrauma
public readonly float ResetTime;
+ ///
+ /// Used to force an event set based on how many other locations have been discovered before this. (Used for campaign tutorial event sets.)
+ ///
+ public readonly int ForceAtDiscoveredNr;
+
+ ///
+ /// Used to force an event set based on how many other outposts have been visited before this. (Used for campaign tutorial event sets.)
+ ///
+ public readonly int ForceAtVisitedNr;
+
+ public readonly bool CampaignTutorialOnly;
+
public readonly struct SubEventPrefab
{
public SubEventPrefab(Either prefabOrIdentifiers, float? commonness, float? probability)
@@ -269,9 +290,18 @@ namespace Barotrauma
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);
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false));
ResetTime = element.GetAttributeFloat("resettime", 0);
+ CampaignTutorialOnly = element.GetAttributeBool(nameof(CampaignTutorialOnly), false);
+
+ ForceAtDiscoveredNr = element.GetAttributeInt(nameof(ForceAtDiscoveredNr), -1);
+ ForceAtVisitedNr = element.GetAttributeInt(nameof(ForceAtVisitedNr), -1);
+ if (ForceAtDiscoveredNr >= 0 && ForceAtVisitedNr >= 0)
+ {
+ DebugConsole.ThrowError($"Error with event set \"{Identifier}\" - both ForceAtDiscoveredNr and ForceAtVisitedNr are defined, this could lead to unexpected behavior");
+ }
DefaultCommonness = element.GetAttributeFloat("commonness", 1.0f);
foreach (var subElement in element.Elements())
@@ -489,6 +519,11 @@ namespace Barotrauma
}
}
+ public override string ToString()
+ {
+ return $"{base.ToString()} ({Identifier.Value})";
+ }
+
public override void Dispose() { }
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs
index f203441b6..e826d0897 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs
@@ -378,7 +378,7 @@ namespace Barotrauma
IEnumerable crewCharacters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
// use multipliers here so that we can easily add them together without introducing multiplicative XP stacking
- var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f);
+ var experienceGainMultiplier = new AbilityMissionExperienceGainMultiplier(this, 1f);
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplier));
crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier));
@@ -386,13 +386,20 @@ 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);
}
#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
- (c.Character?.Info ?? c.CharacterInfo)?.GiveExperience(experienceGain, isMissionExperience: true);
+ 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);
}
#endif
@@ -619,4 +626,16 @@ namespace Barotrauma
public Mission Mission { get; set; }
}
+ class AbilityMissionExperienceGainMultiplier : AbilityObject, IAbilityValue, IAbilityMission
+ {
+ public AbilityMissionExperienceGainMultiplier(Mission mission, float missionExperienceGainMultiplier)
+ {
+ Value = missionExperienceGainMultiplier;
+ Mission = mission;
+ }
+
+ public float Value { get; set; }
+ public Mission Mission { get; set; }
+ }
+
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs
index 9ea8ff2a0..4efce93a4 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs
@@ -218,6 +218,8 @@ namespace Barotrauma.Extensions
return new Dictionary(immutableDictionary);
}
+ public static NetCollection ToNetCollection(this IEnumerable enumerable) => new NetCollection(enumerable.ToImmutableArray());
+
///
/// Returns whether a given collection has at least a certain amount
/// of elements for which the predicate returns true.
diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs
index 76ba0fc8b..32dc12d72 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs
@@ -1,10 +1,15 @@
#nullable enable
using Microsoft.Xna.Framework;
using System;
-using System.Linq;
namespace Barotrauma
{
+ public enum FactionAffiliation
+ {
+ Affiliated,
+ Neutral
+ }
+
class Faction
{
public Reputation Reputation { get; }
@@ -16,11 +21,25 @@ namespace Barotrauma
Reputation = new Reputation(metadata, this, prefab.MinReputation, prefab.MaxReputation, prefab.InitialReputation);
}
- public bool IsAffiliated()
+ ///
+ /// Get what kind of affiliation this faction has towards the player depending on who they chose to side with via talents
+ ///
+ ///
+ public FactionAffiliation GetPlayerAffiliationStatus()
{
- if (GameMain.GameSession?.Campaign?.Factions.MaxBy(static f => f.Reputation.Value) is not { } highestFaction) { return false; }
+ float affiliation = 1f;
+ foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
+ {
+ if (character.Info is not { } info) { continue; }
- return highestFaction.Reputation.Value < 0 || Prefab.Identifier == highestFaction.Prefab.Identifier;
+ affiliation *= 1f + info.GetSavedStatValue(StatTypes.Affiliation, Prefab.Identifier);
+ }
+
+ return affiliation switch
+ {
+ >= 1f => FactionAffiliation.Affiliated,
+ _ => FactionAffiliation.Neutral
+ };
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs
index 4409e74f7..dfe0205af 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs
@@ -749,6 +749,7 @@ namespace Barotrauma
location.LevelData = new LevelData(location, location.Biome.AdjustedMaxDifficulty);
location.Reset();
}
+ Map.ClearLocationHistory();
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
Map.SelectLocation(-1);
if (Map.Radiation != null)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignSettings.cs
index 15c864bd7..791d852b4 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignSettings.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignSettings.cs
@@ -18,6 +18,9 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes)]
public string PresetName { get; set; } = string.Empty;
+ [Serialize(true, IsPropertySaveable.Yes)]
+ public bool TutorialEnabled { get; set; }
+
[Serialize(false, IsPropertySaveable.Yes), NetworkSerialize]
public bool RadiationEnabled { get; set; }
@@ -104,7 +107,9 @@ namespace Barotrauma
private static int GetAddedMissionCount()
{
- return GameSession.GetSessionCrewCharacters(CharacterType.Both).Max(static character => (int)character.GetStatValue(StatTypes.ExtraMissionCount));
+ var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
+ if (!characters.Any()) { return 0; }
+ return characters.Max(static character => (int)character.GetStatValue(StatTypes.ExtraMissionCount));
}
}
}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs
index 9e1e19aa6..a84b1a7c5 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs
@@ -133,13 +133,13 @@ namespace Barotrauma
}
partial void InitProjSpecific();
-
+
public static string GetCharacterDataSavePath(string savePath)
{
- return Path.Combine(SaveUtil.MultiplayerSaveFolder, Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml");
+ return Path.Combine(Path.GetDirectoryName(savePath), Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml");
}
- public string GetCharacterDataSavePath()
+ public static string GetCharacterDataSavePath()
{
return GetCharacterDataSavePath(GameMain.GameSession.SavePath);
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/Tutorials/TutorialPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/Tutorials/TutorialPrefab.cs
index a9942470b..bb9438b28 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/Tutorials/TutorialPrefab.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/Tutorials/TutorialPrefab.cs
@@ -29,6 +29,14 @@ namespace Barotrauma
public readonly Sprite Banner;
+ public readonly EndMessageInfo EndMessage;
+
+ public enum EndType { None, Continue, Restart }
+
+ public readonly record struct EndMessageInfo(
+ EndType EndType,
+ Identifier NextTutorialIdentifier);
+
public TutorialPrefab(ContentFile file, ContentXElement element) : base(file, element.GetAttributeIdentifier("identifier", ""))
{
Order = element.GetAttributeInt("order", int.MaxValue);
@@ -59,6 +67,13 @@ namespace Barotrauma
}
EventIdentifier = element.GetChildElement("scriptedevent")?.GetAttributeIdentifier("identifier", "") ?? Identifier.Empty;
+
+ if (element.GetChildElement("endmessage") is ContentXElement endMessageElement)
+ {
+ EndMessage = new EndMessageInfo(
+ EndType: endMessageElement.GetAttributeEnum("type", EndType.None),
+ NextTutorialIdentifier: endMessageElement.GetAttributeIdentifier("nexttutorial", Identifier.Empty));
+ }
}
public CharacterInfo GetTutorialCharacterInfo()
diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs
index 26bb6fc58..75e39a27f 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs
@@ -582,6 +582,9 @@ namespace Barotrauma
}
}
+#if CLIENT
+ ObjectiveManager.ResetObjectives();
+#endif
EventManager?.StartRound(Level.Loaded);
SteamAchievementManager.OnStartRound();
@@ -847,6 +850,7 @@ namespace Barotrauma
if (GameMain.NetLobbyScreen != null) { GameMain.NetLobbyScreen.OnRoundEnded(); }
TabMenu.OnRoundEnded();
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
+ ObjectiveManager.ResetUI();
#endif
SteamAchievementManager.OnRoundEnded(this);
diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/MedicalClinic.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/MedicalClinic.cs
index 91b63855f..2d25da2dc 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/MedicalClinic.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/MedicalClinic.cs
@@ -9,7 +9,7 @@ using Barotrauma.Networking;
namespace Barotrauma
{
- internal partial class MedicalClinic
+ internal sealed partial class MedicalClinic
{
public enum NetworkHeader
{
@@ -18,7 +18,8 @@ namespace Barotrauma
ADD_PENDING,
REMOVE_PENDING,
CLEAR_PENDING,
- HEAL_PENDING
+ HEAL_PENDING,
+ ADD_EVERYTHING_TO_PENDING
}
public enum AfflictionSeverity
@@ -43,23 +44,10 @@ namespace Barotrauma
}
[NetworkSerialize]
- public struct NetHealRequest : INetSerializableStruct
- {
- public HealRequestResult Result;
- }
+ public readonly record struct NetHealRequest(HealRequestResult Result) : INetSerializableStruct;
[NetworkSerialize]
- public struct NetRemovedAffliction : INetSerializableStruct
- {
- public NetCrewMember CrewMember;
- public NetAffliction Affliction;
- }
-
- public struct NetPendingCrew : INetSerializableStruct
- {
- [NetworkSerialize(ArrayMaxSize = CrewManager.MaxCrewSize)]
- public NetCrewMember[] CrewMembers;
- }
+ public readonly record struct NetRemovedAffliction(NetCrewMember CrewMember, NetAffliction Affliction) : INetSerializableStruct;
public struct NetAffliction : INetSerializableStruct
{
@@ -87,7 +75,7 @@ namespace Barotrauma
}
// between 0.1 and 0.5
- if (normalizedStrength > 0.1f && normalizedStrength < 0.5f)
+ if (normalizedStrength is > 0.1f and < 0.5f)
{
return AfflictionSeverity.Medium;
}
@@ -146,17 +134,23 @@ namespace Barotrauma
}
}
- public struct NetCrewMember : INetSerializableStruct
+ public record struct NetCrewMember : INetSerializableStruct
{
[NetworkSerialize]
public int CharacterInfoID;
[NetworkSerialize]
- public NetAffliction[] Afflictions;
+ public ImmutableArray Afflictions;
- public CharacterInfo CharacterInfo
+ public NetCrewMember(CharacterInfo info)
{
- set => CharacterInfoID = value.GetIdentifierUsingOriginalName();
+ CharacterInfoID = info.GetIdentifierUsingOriginalName();
+ Afflictions = ImmutableArray.Empty;
+ }
+
+ public NetCrewMember(CharacterInfo info, ImmutableArray afflictions): this(info)
+ {
+ Afflictions = afflictions;
}
public readonly CharacterInfo? FindCharacterInfo(ImmutableArray crew)
@@ -194,11 +188,11 @@ namespace Barotrauma
private static bool IsOutpostInCombat()
{
- if (!(Level.Loaded is { Type: LevelData.LevelType.Outpost })) { return false; }
+ if (Level.Loaded is not { Type: LevelData.LevelType.Outpost }) { return false; }
- IEnumerable crew = GetCrewCharacters().Where(c => c.Character != null).Select(c => c.Character).ToImmutableHashSet();
+ IEnumerable crew = GetCrewCharacters().Where(static c => c.Character != null).Select(static c => c.Character).ToImmutableHashSet();
- foreach (Character npc in Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.FriendlyNPC))
+ foreach (Character npc in Character.CharacterList.Where(static c => c.TeamID == CharacterTeamType.FriendlyNPC))
{
bool isInCombatWithCrew = !npc.IsInstigator && npc.AIController is HumanAIController { ObjectiveManager: { CurrentObjective: AIObjectiveCombat combatObjective } } && crew.Contains(combatObjective.Enemy);
if (isInCombatWithCrew) { return true; }
@@ -238,6 +232,20 @@ namespace Barotrauma
PendingHeals.Clear();
}
+ private void AddEverythingToPending()
+ {
+ foreach (CharacterInfo info in GetCrewCharacters())
+ {
+ if (info.Character?.CharacterHealth is not { } health) { continue; }
+
+ var afflictions = GetAllAfflictions(health);
+
+ if (afflictions.Length is 0) { continue; }
+
+ InsertPendingCrewMember(new NetCrewMember(info, afflictions));
+ }
+ }
+
private void RemovePendingAffliction(NetCrewMember crewMember, NetAffliction affliction)
{
foreach (NetCrewMember listMember in PendingHeals.ToList())
@@ -255,7 +263,7 @@ namespace Barotrauma
newAfflictions.Add(pendingAffliction);
}
- pendingMember.Afflictions = newAfflictions.ToArray();
+ pendingMember.Afflictions = newAfflictions.ToImmutableArray();
}
if (!pendingMember.Afflictions.Any()) { continue; }
@@ -280,9 +288,9 @@ namespace Barotrauma
static float GetShowTreshold(Affliction affliction) => Math.Max(0, Math.Min(affliction.Prefab.ShowIconToOthersThreshold, affliction.Prefab.ShowInHealthScannerThreshold));
}
- private NetAffliction[] GetAllAfflictions(CharacterHealth health)
+ private ImmutableArray GetAllAfflictions(CharacterHealth health)
{
- IEnumerable rawAfflictions = health.GetAllAfflictions().Where(a => IsHealable(a));
+ IEnumerable rawAfflictions = health.GetAllAfflictions().Where(IsHealable);
List afflictions = new List();
@@ -305,12 +313,12 @@ namespace Barotrauma
afflictions.Add(newAffliction);
}
- return afflictions.ToArray();
+ return afflictions.ToImmutableArray();
static int GetHealPrice(Affliction affliction) => (int)(affliction.Prefab.BaseHealCost + (affliction.Prefab.HealCostMultiplier * affliction.Strength));
}
- public int GetTotalCost() => PendingHeals.SelectMany(h => h.Afflictions).Aggregate(0, (current, affliction) => current + affliction.Price);
+ public int GetTotalCost() => PendingHeals.SelectMany(static h => h.Afflictions).Aggregate(0, static (current, affliction) => current + affliction.Price);
private int GetAdjustedPrice(int price) => campaign?.Map?.CurrentLocation is { Type: { HasOutpost: true } } currentLocation ? currentLocation.GetAdjustedHealCost(price) : int.MaxValue;
@@ -325,7 +333,7 @@ namespace Barotrauma
}
#endif
- return Character.CharacterList.Where(c => c.Info != null && c.TeamID == CharacterTeamType.Team1).Select(c => c.Info).ToImmutableArray();
+ return Character.CharacterList.Where(static c => c.Info != null && c.TeamID == CharacterTeamType.Team1).Select(static c => c.Info).ToImmutableArray();
}
#if DEBUG && CLIENT
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs
index a660bbbf3..6872f409c 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs
@@ -493,6 +493,10 @@ namespace Barotrauma
base.PutItem(item, i, user, removeItem, createNetworkEvent);
#if CLIENT
CreateSlots();
+ if (character == Character.Controlled)
+ {
+ HintManager.OnObtainedItem(character, item);
+ }
#endif
if (item.CampaignInteractionType == CampaignMode.InteractionType.Cargo)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs
index aa5dc7d28..9572b9953 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs
@@ -843,10 +843,18 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (item.body == null || !item.body.Enabled) { return; }
+
+ Character owner = picker ?? item.GetRootInventoryOwner() as Character;
+
+ if (owner != null)
+ {
+ ApplyStatusEffects(ActionType.OnActive, deltaTime, owner);
+ }
+
if (picker == null || !picker.HasEquippedItem(item))
{
if (Pusher != null) { Pusher.Enabled = false; }
- if (attachTargetCell == null) { IsActive = false; }
+ if (attachTargetCell == null && owner == null) { IsActive = false; }
return;
}
@@ -855,23 +863,7 @@ namespace Barotrauma.Items.Components
Drawable = true;
}
- Vector2 swing = Vector2.Zero;
- if (swingAmount != Vector2.Zero && !picker.IsUnconscious && picker.Stun <= 0.0f)
- {
- swingState += deltaTime;
- swingState %= 1.0f;
- if (SwingWhenHolding ||
- (SwingWhenAiming && picker.IsKeyDown(InputType.Aim)) ||
- (SwingWhenUsing && picker.IsKeyDown(InputType.Aim) && picker.IsKeyDown(InputType.Shoot)))
- {
- swing = swingAmount * new Vector2(
- PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f, swingState * SwingSpeed * 0.1f) - 0.5f,
- PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f + 0.5f, swingState * SwingSpeed * 0.1f + 0.5f) - 0.5f);
- }
- }
-
- ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
-
+ UpdateSwingPos(deltaTime, out Vector2 swingPos);
if (item.body.Dir != picker.AnimController.Dir)
{
item.FlipX(relativeToSub: false);
@@ -884,7 +876,7 @@ namespace Barotrauma.Items.Components
scaledHandlePos[0] = handlePos[0] * item.Scale;
scaledHandlePos[1] = handlePos[1] * item.Scale;
bool aim = picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero && picker.CanAim;
- picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swing, aimPos + swing, aim, holdAngle);
+ picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swingPos, aimPos + swingPos, aim, holdAngle);
if (!aim)
{
var rope = GetRope();
@@ -921,6 +913,24 @@ namespace Barotrauma.Items.Components
}
}
+ public void UpdateSwingPos(float deltaTime, out Vector2 swingPos)
+ {
+ swingPos = Vector2.Zero;
+ if (swingAmount != Vector2.Zero && !picker.IsUnconscious && picker.Stun <= 0.0f)
+ {
+ swingState += deltaTime;
+ swingState %= 1.0f;
+ if (SwingWhenHolding ||
+ (SwingWhenAiming && picker.IsKeyDown(InputType.Aim)) ||
+ (SwingWhenUsing && picker.IsKeyDown(InputType.Aim) && picker.IsKeyDown(InputType.Shoot)))
+ {
+ swingPos = swingAmount * new Vector2(
+ PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f, swingState * SwingSpeed * 0.1f) - 0.5f,
+ PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f + 0.5f, swingState * SwingSpeed * 0.1f + 0.5f) - 0.5f);
+ }
+ }
+ }
+
public override void ReceiveSignal(Signal signal, Connection connection)
{
//do nothing
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs
index f0b500c43..b1254de84 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs
@@ -214,8 +214,9 @@ namespace Barotrauma.Items.Components
bool aim = item.RequireAimToUse && picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim;
if (aim)
{
+ UpdateSwingPos(deltaTime, out Vector2 swingPos);
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 3f, MathHelper.PiOver4));
- ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
+ ac.HoldItem(deltaTime, item, handlePos, aimPos + swingPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
if (ac.InWater)
{
ac.LockFlippingUntil = (float)Timing.TotalTime + Reload;
@@ -392,34 +393,35 @@ namespace Barotrauma.Items.Components
float damageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
damageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier);
+ Character user = User;
Limb targetLimb = target.UserData as Limb;
Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
if (Attack != null)
{
- Attack.SetUser(User);
+ Attack.SetUser(user);
Attack.DamageMultiplier = damageMultiplier;
if (targetLimb != null)
{
if (targetLimb.character.Removed) { return; }
targetLimb.character.LastDamageSource = item;
- Attack.DoDamageToLimb(User, targetLimb, item.WorldPosition, 1.0f);
+ Attack.DoDamageToLimb(user, targetLimb, item.WorldPosition, 1.0f);
}
else if (targetCharacter != null)
{
if (targetCharacter.Removed) { return; }
targetCharacter.LastDamageSource = item;
- Attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f);
+ Attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
}
else if ((target.UserData as Structure ?? targetFixture.UserData as Structure) is Structure targetStructure)
{
if (targetStructure.Removed) { return; }
- Attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
+ Attack.DoDamage(user, targetStructure, item.WorldPosition, 1.0f);
}
else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
{
if (targetItem.Removed) { return; }
- var attackResult = Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
+ var attackResult = Attack.DoDamage(user, targetItem, item.WorldPosition, 1.0f);
#if CLIENT
if (attackResult.Damage > 0.0f && targetItem.Prefab.ShowHealthBar)
{
@@ -435,7 +437,7 @@ namespace Barotrauma.Items.Components
else if (target.UserData is Holdable holdable && holdable.CanPush)
{
if (holdable.Item.Removed) { return; }
- Attack.DoDamage(User, holdable.Item, item.WorldPosition, 1.0f);
+ Attack.DoDamage(user, holdable.Item, item.WorldPosition, 1.0f);
RestoreCollision();
hitting = false;
User = null;
@@ -448,29 +450,32 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
- bool success = Rand.Range(0.0f, 0.5f) < DegreeOfSuccess(User);
-
-#if SERVER
- if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
+ ActionType conditionalActionType = ActionType.OnSuccess;
+ if (user != null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(user))
{
- GameMain.Server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(
- success ? ActionType.OnUse : ActionType.OnFailure,
- targetItemComponent: null,
- targetCharacter, targetLimb));
-
- string logStr = picker?.LogName + " used " + item.Name;
- if (item.ContainedItems != null && item.ContainedItems.Any())
- {
- logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
- }
- logStr += " on " + targetCharacter.LogName + ".";
- Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
+ conditionalActionType = ActionType.OnFailure;
+ }
+ if (GameMain.NetworkMember is { IsServer: true } server && targetCharacter != null)
+ {
+ server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, targetItemComponent: null, targetCharacter, targetLimb));
+ server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnUse, targetItemComponent: null, targetCharacter, targetLimb));
+ #if SERVER
+ if (GameMain.Server != null) //TODO: Log structure hits
+ {
+ string logStr = picker?.LogName + " used " + item.Name;
+ if (item.ContainedItems != null && item.ContainedItems.Any())
+ {
+ logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
+ }
+ logStr += " on " + targetCharacter.LogName + ".";
+ Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
+ }
+ #endif
}
-#endif
-
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
{
- ApplyStatusEffects(success ? ActionType.OnUse : ActionType.OnFailure, 1.0f, targetCharacter, targetLimb, user: User, afflictionMultiplier: damageMultiplier);
+ ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, user: user, afflictionMultiplier: damageMultiplier);
+ ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: user, afflictionMultiplier: damageMultiplier);
}
if (DeleteOnUse)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs
index cadea84be..fa2cf6dbd 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs
@@ -23,6 +23,8 @@ namespace Barotrauma.Items.Components
[Serialize(0.0f, IsPropertySaveable.No, description: "The force to apply to the user's body."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
public float Force { get; set; }
+ [Serialize(true, IsPropertySaveable.No, description: "If the item is held in RightHand or LeftHand, apply extra force there")]
+ public bool ApplyToHands { get; set; }
#if CLIENT
private string particles;
[Serialize("", IsPropertySaveable.No, description: "The name of the particle prefab the item emits when used.")]
@@ -70,13 +72,16 @@ namespace Barotrauma.Items.Components
character.AnimController.Collider.ApplyForce(propulsion);
- if (character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand))
- {
- character.AnimController.GetLimb(LimbType.RightHand)?.body.ApplyForce(propulsion);
- }
- if (character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand))
- {
- character.AnimController.GetLimb(LimbType.LeftHand)?.body.ApplyForce(propulsion);
+ if (ApplyToHands)
+ {
+ if (character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand))
+ {
+ character.AnimController.GetLimb(LimbType.RightHand)?.body.ApplyForce(propulsion);
+ }
+ if (character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand))
+ {
+ character.AnimController.GetLimb(LimbType.LeftHand)?.body.ApplyForce(propulsion);
+ }
}
#if CLIENT
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs
index 998d7d7c1..054856773 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs
@@ -32,6 +32,20 @@ namespace Barotrauma.Items.Components
set { reload = Math.Max(value, 0.0f); }
}
+ [Serialize(0f, IsPropertySaveable.No, description: "Weapons skill requirement to reload at normal speed.")]
+ public float ReloadSkillRequirement
+ {
+ get;
+ set;
+ }
+
+ [Serialize(1.0f, IsPropertySaveable.No, description: "Reload time at 0 skill level. Reload time scales with skill level up to the Weapons skill requirement.")]
+ public float ReloadNoSkill
+ {
+ get;
+ set;
+ }
+
[Serialize(false, IsPropertySaveable.No, description: "Tells the AI to hold the trigger down when it uses this weapon")]
public bool HoldTrigger
{
@@ -39,7 +53,7 @@ namespace Barotrauma.Items.Components
set;
}
- [Serialize(1, IsPropertySaveable.No, description: "How projectiles the weapon launches when fired once.")]
+ [Serialize(1, IsPropertySaveable.No, description: "How many projectiles the weapon launches when fired once.")]
public int ProjectileCount
{
get;
@@ -60,6 +74,23 @@ namespace Barotrauma.Items.Components
set;
}
+ [Serialize(0.0f, IsPropertySaveable.No, description: "The impulse applied to the physics body of the projectile (the higher the impulse, the faster the projectiles are launched). Sum of weapon + projectile.")]
+ public float LaunchImpulse
+ {
+ get;
+ set;
+ }
+
+ [Serialize(0.0f, IsPropertySaveable.Yes, description: "Percentage of damage mitigation ignored when hitting armored body parts (deflecting limbs). Sum of weapon + projectile."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1f)]
+ public float Penetration { get; private set; }
+
+ [Serialize(1f, IsPropertySaveable.Yes, description: "Weapon's damage modifier")]
+ public float WeaponDamageModifier
+ {
+ get;
+ private set;
+ }
+
[Serialize(0f, IsPropertySaveable.Yes, description: "The time required for a charge-type turret to charge up before able to fire.")]
public float MaxChargeTime
{
@@ -99,6 +130,12 @@ namespace Barotrauma.Items.Components
// TODO: should define this in xml if we have ranged weapons that don't require aim to use
item.RequireAimToUse = true;
characterUsable = true;
+
+ if (ReloadSkillRequirement > 0 && ReloadNoSkill <= reload)
+ {
+ DebugConsole.AddWarning($"Invalid XML at {item.Name}: ReloadNoSkill is lower or equal than it's reload skill, despite having ReloadSkillRequirement.");
+ }
+
InitProjSpecific(element);
}
@@ -167,7 +204,15 @@ namespace Barotrauma.Items.Components
if (currentChargeTime < MaxChargeTime) { return false; }
IsActive = true;
- ReloadTimer = reload / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f);
+ float baseReloadTime = reload;
+ float weaponSkill = character.GetSkillLevel("weapons");
+ if (ReloadSkillRequirement > 0 && ReloadNoSkill > reload && weaponSkill < ReloadSkillRequirement)
+ {
+ //Examples, assuming 40 weapon skill required: 1 - 40/40 = 0 ... 1 - 0/40 = 1 ... 1 - 20 / 40 = 0.5
+ float reloadFailure = MathHelper.Clamp(1 - (weaponSkill / ReloadSkillRequirement), 0, 1);
+ baseReloadTime = MathHelper.Lerp(reload, ReloadNoSkill, reloadFailure);
+ }
+ ReloadTimer = baseReloadTime / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f);
currentChargeTime = 0f;
if (character != null)
@@ -218,9 +263,9 @@ namespace Barotrauma.Items.Components
{
lastProjectile?.Item.GetComponent()?.Snap();
}
- float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier);
+ float damageMultiplier = (1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier)) * WeaponDamageModifier;
projectile.Launcher = item;
- projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: ignoredBodies.ToList(), createNetworkEvent: false, damageMultiplier);
+ projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: ignoredBodies.ToList(), createNetworkEvent: false, damageMultiplier, LaunchImpulse);
projectile.Item.GetComponent()?.Attach(Item, projectile.Item);
if (i == 0)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs
index df595ca57..edf91b18e 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs
@@ -1,17 +1,26 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Linq;
-using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Throwable : Holdable
{
- private float throwPos;
- private bool throwing, throwDone;
+ enum ThrowState
+ {
+ None,
+ Initiated,
+ Throwing
+ }
+
+ private const float ThrowAngleStart = -MathHelper.PiOver2, ThrowAngleEnd = MathHelper.PiOver2;
+ private float throwAngle = ThrowAngleStart;
private bool midAir;
+ private ThrowState throwState;
+
+
//continuous collision detection is used while the item is moving faster than this
const float ContinuousCollisionThreshold = 5.0f;
@@ -27,7 +36,6 @@ namespace Barotrauma.Items.Components
public Throwable(Item item, ContentXElement element)
: base(item, element)
{
- //throwForce = ToolBox.GetAttributeFloat(element, "throwforce", 1.0f);
if (aimPos == Vector2.Zero)
{
aimPos = new Vector2(0.6f, 0.1f);
@@ -36,22 +44,21 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
- return characterUsable || character == null; //We do the actual throwing in Aim because Use might be used by chems
+ //actual throwing logic is handled in Update
+ return characterUsable || character == null;
}
public override bool SecondaryUse(float deltaTime, Character character = null)
{
- if (!throwDone) return false; //This should only be triggered in update
- throwDone = false;
- return true;
+ //actual throwing logic is handled in Update - SecondaryUse only triggers when the item is thrown
+ return false;
}
public override void Drop(Character dropper)
{
base.Drop(dropper);
-
- throwing = false;
- throwPos = 0.0f;
+ throwState = ThrowState.None;
+ throwAngle = ThrowAngleStart;
}
public override void UpdateBroken(float deltaTime, Camera cam)
@@ -100,13 +107,22 @@ namespace Barotrauma.Items.Components
return;
}
- if (picker.IsKeyDown(InputType.Aim) && picker.IsKeyHit(InputType.Shoot)) { throwing = true; }
- if (!picker.IsKeyDown(InputType.Aim) && !throwing) { throwPos = 0.0f; }
- bool aim = picker.IsKeyDown(InputType.Aim) && picker.CanAim;
+ if (throwState != ThrowState.Throwing)
+ {
+ if (picker.IsKeyDown(InputType.Aim))
+ {
+ if (picker.IsKeyDown(InputType.Shoot)) { throwState = ThrowState.Initiated; }
+ }
+ else if (throwState != ThrowState.Initiated)
+ {
+ throwAngle = ThrowAngleStart;
+ }
+ }
+ bool aim = picker.IsKeyDown(InputType.Aim) && picker.CanAim;
if (picker.IsDead || !picker.AllowInput)
{
- throwing = false;
+ throwState = ThrowState.None;
aim = false;
}
@@ -124,25 +140,29 @@ namespace Barotrauma.Items.Components
item.Submarine = picker.Submarine;
- if (!throwing)
+ if (throwState != ThrowState.Throwing)
{
- if (aim)
+ if (aim || throwState == ThrowState.Initiated)
{
- throwPos = MathUtils.WrapAnglePi(System.Math.Min(throwPos + deltaTime * 5.0f, MathHelper.PiOver2));
- ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwPos);
+ throwAngle = System.Math.Min(throwAngle + deltaTime * 8.0f, ThrowAngleEnd);
+ ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwAngle);
+ if (throwAngle >= ThrowAngleEnd && throwState == ThrowState.Initiated)
+ {
+ throwState = ThrowState.Throwing;
+ }
}
else
{
- throwPos = 0;
+ throwAngle = ThrowAngleStart;
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle);
}
}
else
{
- throwPos = MathUtils.WrapAnglePi(throwPos - deltaTime * 15.0f);
- ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwPos);
+ throwAngle = MathUtils.WrapAnglePi(throwAngle - deltaTime * 15.0f);
+ ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwAngle);
- if (throwPos < 0)
+ if (throwAngle < 0)
{
Vector2 throwVector = Vector2.Normalize(picker.CursorWorldPosition - picker.WorldPosition);
//throw upwards if cursor is at the position of the character
@@ -180,8 +200,7 @@ namespace Barotrauma.Items.Components
Limb rightHand = ac.GetLimb(LimbType.RightHand);
item.body.AngularVelocity = rightHand.body.AngularVelocity;
- throwPos = 0;
- throwDone = true;
+ throwAngle = ThrowAngleStart;
IsActive = true;
if (GameMain.NetworkMember is { IsServer: true })
@@ -193,7 +212,7 @@ namespace Barotrauma.Items.Components
//Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, CurrentThrower, user: CurrentThrower);
}
- throwing = false;
+ throwState = ThrowState.None;
}
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs
index a1d69ce93..fac2fc0f0 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs
@@ -65,8 +65,16 @@ namespace Barotrauma.Items.Components
public int Capacity
{
get { return capacity; }
- set { capacity = Math.Max(value, 0); }
+ private set
+ {
+ capacity = Math.Max(value, 0);
+ MainContainerCapacity = value;
+ }
}
+ ///
+ /// The capacity of the main container without taking the sub containers into account. Only differs when there's a sub container defined for the component.
+ ///
+ public int MainContainerCapacity { get; private set; }
//how many items can be contained
private int maxStackSize;
@@ -229,6 +237,9 @@ namespace Barotrauma.Items.Components
public ImmutableHashSet ContainableItemIdentifiers => containableItemIdentifiers;
public List ContainableItems { get; }
+ public List AllSubContainableItems { get; }
+
+ public readonly bool HasSubContainers;
public ItemContainer(Item item, ContentXElement element)
: base(item, element)
@@ -251,6 +262,7 @@ namespace Barotrauma.Items.Components
break;
case "subcontainer":
totalCapacity += subElement.GetAttributeInt("capacity", 1);
+ HasSubContainers = true;
break;
}
}
@@ -270,7 +282,7 @@ namespace Barotrauma.Items.Components
int subCapacity = subElement.GetAttributeInt("capacity", 1);
int subMaxStackSize = subElement.GetAttributeInt("maxstacksize", maxStackSize);
- List subContainableItems = null;
+ var subContainableItems = new List();
foreach (var subSubElement in subElement.Elements())
{
if (subSubElement.Name.ToString().ToLowerInvariant() != "containable") { continue; }
@@ -281,8 +293,9 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - containable with no identifiers.");
continue;
}
- subContainableItems ??= new List();
subContainableItems.Add(containable);
+ AllSubContainableItems ??= new List();
+ AllSubContainableItems.Add(containable);
}
for (int i = subContainerIndex; i < subContainerIndex + subCapacity; i++)
@@ -357,6 +370,14 @@ namespace Barotrauma.Items.Components
//no need to Update() if this item has no statuseffects and no physics body
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
+
+ if (IsActive && item.GetRootInventoryOwner() is Character owner &&
+ owner.HasEquippedItem(item, predicate: slot => slot.HasFlag(InvSlotType.LeftHand) || slot.HasFlag(InvSlotType.RightHand)))
+ {
+ // Set the contained items active if there's an item inserted inside the container. Enables e.g. the rifle flashlight when it's attached to the rifle (put inside of it).
+ SetContainedActive(true);
+ }
+
OnContainedItemsChanged.Invoke(this);
}
@@ -409,6 +430,20 @@ namespace Barotrauma.Items.Components
return false;
}
+ public override void FlipX(bool relativeToSub)
+ {
+ base.FlipX(relativeToSub);
+ if (HideItems) { return; }
+ if (item.body == null) { return; }
+ foreach (Item containedItem in Inventory.AllItems)
+ {
+ if (containedItem.body != null && containedItem.body.Enabled && containedItem.body.Dir != item.body.Dir)
+ {
+ containedItem.FlipX(relativeToSub);
+ }
+ }
+ }
+
public override void Update(float deltaTime, Camera cam)
{
if (!string.IsNullOrEmpty(SpawnWithId) && !alwaysContainedItemsSpawned)
@@ -477,7 +512,7 @@ namespace Barotrauma.Items.Components
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
- targets.AddRange(effect.GetNearbyTargets(item.WorldPosition, targets));
+ effect.AddNearbyTargets(item.WorldPosition, targets);
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
}
}
@@ -582,11 +617,53 @@ namespace Barotrauma.Items.Components
public override void Drop(Character dropper)
{
IsActive = true;
+ SetContainedActive(false);
}
public override void Equip(Character character)
{
IsActive = true;
+ if (character != null && character.HasEquippedItem(item, predicate: slot => slot.HasFlag(InvSlotType.LeftHand) || slot.HasFlag(InvSlotType.RightHand)))
+ {
+ SetContainedActive(true);
+ }
+ }
+
+ private void SetContainedActive(bool active)
+ {
+ foreach (Item containedItem in Inventory.AllItems)
+ {
+ RelatedItem containableItem = FindContainableItem(containedItem);
+ if (containableItem != null && containableItem.SetActive)
+ {
+ foreach (var ic in containedItem.Components)
+ {
+ ic.IsActive = active;
+ }
+ if (containedItem.body != null)
+ {
+ containedItem.body.Enabled = active;
+ if (active)
+ {
+ containedItem.body.PhysEnabled = false;
+ }
+ }
+ }
+ }
+ if (active)
+ {
+ FlipX(false);
+ }
+ }
+
+ private RelatedItem FindContainableItem(Item item)
+ {
+ var relatedItem = ContainableItems?.FirstOrDefault(ci => ci.MatchesItem(item));
+ if (relatedItem == null && AllSubContainableItems != null)
+ {
+ relatedItem = AllSubContainableItems.FirstOrDefault(ci => ci.MatchesItem(item));
+ }
+ return relatedItem;
}
public override void ReceiveSignal(Signal signal, Connection connection)
@@ -604,6 +681,7 @@ namespace Barotrauma.Items.Components
}
}
+#warning There's some code duplication here and in DrawContainedItems() method, but it's not straightforward to get rid of it, because of slightly different logic and the usage of draw positions vs. positions etc. Should probably be splitted into smaller methods.
public void SetContainedItemPositions()
{
Vector2 transformedItemPos = ItemPos * item.Scale;
@@ -657,29 +735,70 @@ namespace Barotrauma.Items.Components
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemPos += item.Position;
}
- }
-
- float currentRotation = itemRotation;
- if (item.body != null)
- {
- currentRotation *= item.body.Dir;
- currentRotation += item.body.Rotation;
- }
- else
- {
- currentRotation += -item.RotationRad;
}
int i = 0;
Vector2 currentItemPos = transformedItemPos;
foreach (Item contained in Inventory.AllItems)
{
+ Vector2 itemPos = currentItemPos;
+ var relatedItem = FindContainableItem(contained);
+ if (relatedItem != null)
+ {
+ if (relatedItem.Hide.HasValue && relatedItem.Hide.Value) { continue; }
+ if (relatedItem.ItemPos.HasValue)
+ {
+ Vector2 pos = relatedItem.ItemPos.Value;
+ if (item.body != null)
+ {
+ Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
+ pos.X *= item.body.Dir;
+ itemPos = Vector2.Transform(pos, transform) + item.body.Position;
+ }
+ else
+ {
+ itemPos = pos;
+ // This code is aped based on above. Not tested.
+ if (item.FlippedX)
+ {
+ itemPos.X = -itemPos.X;
+ itemPos.X += item.Rect.Width;
+ }
+ if (item.FlippedY)
+ {
+ itemPos.Y = -itemPos.Y;
+ itemPos.Y -= item.Rect.Height;
+ }
+ itemPos += new Vector2(item.Rect.X, item.Rect.Y);
+ if (Math.Abs(item.RotationRad) > 0.01f)
+ {
+ Matrix transform = Matrix.CreateRotationZ(item.RotationRad);
+ itemPos = Vector2.Transform(itemPos - item.Position, transform) + item.Position;
+ }
+ }
+ }
+ }
+
if (contained.body != null)
{
try
{
- Vector2 simPos = ConvertUnits.ToSimUnits(currentItemPos);
- contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, currentRotation);
+ Vector2 simPos = ConvertUnits.ToSimUnits(itemPos);
+ float rotation = itemRotation;
+ if (relatedItem != null && relatedItem.Rotation != 0)
+ {
+ rotation = MathHelper.ToRadians(relatedItem.Rotation);
+ }
+ if (item.body != null)
+ {
+ rotation *= item.body.Dir;
+ rotation += item.body.Rotation;
+ }
+ else
+ {
+ rotation += -item.RotationRad;
+ }
+ contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, rotation);
contained.body.SetPrevTransform(contained.body.SimPosition, contained.body.Rotation);
contained.body.UpdateDrawPosition();
}
@@ -695,8 +814,8 @@ namespace Barotrauma.Items.Components
contained.Rect =
new Rectangle(
- (int)(currentItemPos.X - contained.Rect.Width / 2.0f),
- (int)(currentItemPos.Y + contained.Rect.Height / 2.0f),
+ (int)(itemPos.X - contained.Rect.Width / 2.0f),
+ (int)(itemPos.Y + contained.Rect.Height / 2.0f),
contained.Rect.Width, contained.Rect.Height);
contained.Submarine = item.Submarine;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs
index 21179fc74..f8df97352 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs
@@ -459,6 +459,12 @@ namespace Barotrauma.Items.Components
progressTimer = 0.0f;
progressState = 0.0f;
}
+#if CLIENT
+ else
+ {
+ HintManager.OnStartDeconstructing(user, this);
+ }
+#endif
inputContainer.Inventory.Locked = IsActive;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs
index 537366213..e2c8cd0b0 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs
@@ -419,7 +419,7 @@ namespace Barotrauma.Items.Components
}
var fabricationIngredients = new AbilityFabricationItemIngredients(foundAvailableItems);
- user.CheckTalents(AbilityEffectType.OnItemFabricatedIngredients, fabricationIngredients);
+ user?.CheckTalents(AbilityEffectType.OnItemFabricatedIngredients, fabricationIngredients);
foreach (Item availableItem in fabricationIngredients.Items)
{
@@ -559,7 +559,7 @@ namespace Barotrauma.Items.Components
if (fabricatedItem.TargetItem.ConfigElement.GetChildElement("Quality") == null) { return 0; }
int quality = 0;
float floatQuality = 0.0f;
- floatQuality += user.GetStatValue(StatTypes.IncreaseFabricationQuality);
+ floatQuality += user.GetStatValue(StatTypes.IncreaseFabricationQuality, includeSaved: false);
foreach (var tag in fabricatedItem.TargetItem.Tags)
{
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
@@ -740,10 +740,27 @@ namespace Barotrauma.Items.Components
//order by condition (prefer using worst-condition items)
int index = 0;
while (index < availableIngredients[itemIdentifier].Count &&
- availableIngredients[itemIdentifier][index].Condition < item.Condition)
+ compare(item, availableIngredients[itemIdentifier][index], inputContainer.Inventory) < 0)
{
index++;
}
+
+ static int compare(Item item1, Item item2, Inventory inputInventory)
+ {
+ bool item1InInputInventory = item1.ParentInventory == inputInventory;
+ bool item2InInputInventory = item2.ParentInventory == inputInventory;
+ //prefer items in the input inventory
+ if (item1InInputInventory != item2InInputInventory)
+ {
+ return item1InInputInventory ? 1 : -1;
+ }
+ else
+ {
+ //prefer items in worse condition
+ return Math.Sign(item2.Condition - item1.Condition);
+ }
+ }
+
availableIngredients[itemIdentifier].Insert(index, item);
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs
index a3588ac5c..8a3cd4d15 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs
@@ -15,6 +15,8 @@ namespace Barotrauma.Items.Components
public float? ReceivedOxygenAmount,
ReceivedWaterAmount;
+ public double LastOxygenDataTime, LastWaterDataTime;
+
public readonly HashSet Cards = new HashSet();
public bool Distort;
@@ -83,7 +85,7 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
- //periodically reset all hull data
+ //reset data if we haven't received anything in a while
//(so that outdated hull info won't be shown if detectors stop sending signals)
if (DateTime.Now > resetDataTime)
{
@@ -91,8 +93,8 @@ namespace Barotrauma.Items.Components
{
if (!hullData.Distort)
{
- hullData.ReceivedOxygenAmount = null;
- hullData.ReceivedWaterAmount = null;
+ if (Timing.TotalTime > hullData.LastOxygenDataTime + 1.0) { hullData.ReceivedOxygenAmount = null; }
+ if (Timing.TotalTime > hullData.LastWaterDataTime + 1.0) { hullData.ReceivedWaterAmount = null; }
}
}
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
@@ -159,6 +161,7 @@ namespace Barotrauma.Items.Components
//cheating a bit because water detectors don't actually send the water level
bool fromWaterDetector = source.GetComponent() != null;
hullData.ReceivedWaterAmount = null;
+ hullData.LastWaterDataTime = Timing.TotalTime;
if (fromWaterDetector)
{
hullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(sourceHull);
@@ -184,9 +187,10 @@ namespace Barotrauma.Items.Components
oxy = Rand.Range(0.0f, 100.0f);
}
hullData.ReceivedOxygenAmount = oxy;
+ hullData.LastOxygenDataTime = Timing.TotalTime;
foreach (var linked in sourceHull.linkedTo)
{
- if (!(linked is Hull linkedHull)) { continue; }
+ if (linked is not Hull linkedHull) { continue; }
if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData))
{
linkedHullData = new HullData();
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs
index 6e727c30d..250c5b37a 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs
@@ -197,11 +197,6 @@ namespace Barotrauma.Items.Components
if (currentPingIndex != -1)
{
var activePing = activePings[currentPingIndex];
- if (item.AiTarget != null)
- {
- float range = MathUtils.InverseLerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, Range * activePing.State / zoom);
- item.AiTarget.SoundRange = MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, range);
- }
if (activePing.State > 1.0f)
{
aiPingCheckPending = true;
@@ -235,6 +230,11 @@ namespace Barotrauma.Items.Components
for (var pingIndex = 0; pingIndex < activePingsCount;)
{
+ if (item.AiTarget != null)
+ {
+ float range = MathUtils.InverseLerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, Range * activePings[pingIndex].State / zoom);
+ item.AiTarget.SoundRange = Math.Max(item.AiTarget.SoundRange, MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, range));
+ }
if (activePings[pingIndex].State > 1.0f)
{
var lastIndex = --activePingsCount;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs
index 75776f814..90922ec50 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs
@@ -144,6 +144,11 @@ namespace Barotrauma.Items.Components
}
}
+ public float TargetVelocityLengthSquared
+ {
+ get => TargetVelocity.LengthSquared();
+ }
+
public Vector2 SteeringInput
{
get { return steeringInput; }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs
index a56e5afde..741afba2e 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs
@@ -392,6 +392,7 @@ namespace Barotrauma.Items.Components
}
}
User = character;
+ ApplyStatusEffects(ActionType.OnUse, 1.0f, User, user: User);
return true;
}
@@ -916,23 +917,22 @@ namespace Barotrauma.Items.Components
if (character != null) { character.LastDamageSource = item; }
- ActionType actionType = ActionType.OnUse;
- if (_user != null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(_user))
+ ActionType conditionalActionType = ActionType.OnSuccess;
+ if (User != null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(User))
{
- actionType = ActionType.OnFailure;
+ conditionalActionType = ActionType.OnFailure;
}
-
#if CLIENT
- PlaySound(actionType, user: _user);
- PlaySound(ActionType.OnImpact, user: _user);
+ PlaySound(conditionalActionType, user: User);
+ PlaySound(ActionType.OnImpact, user: User);
#endif
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (target.Body.UserData is Limb targetLimb)
{
- ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: _user);
- ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: _user);
+ ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, user: User);
+ ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: User);
var attack = targetLimb.attack;
if (attack != null)
{
@@ -941,8 +941,6 @@ namespace Barotrauma.Items.Components
{
if (effect.type == ActionType.OnImpact)
{
- //effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character, targetLimb.WorldPosition);
-
if (effect.HasTargetType(StatusEffect.TargetType.This))
{
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character, targetLimb.WorldPosition);
@@ -951,32 +949,27 @@ namespace Barotrauma.Items.Components
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
- targets.AddRange(effect.GetNearbyTargets(targetLimb.WorldPosition, targets));
+ effect.AddNearbyTargets(targetLimb.WorldPosition, targets);
effect.Apply(ActionType.OnActive, 1.0f, targetLimb.character, targets);
}
-
}
}
}
-#if SERVER
- if (GameMain.NetworkMember.IsServer)
+ if (GameMain.NetworkMember is { IsServer: true } server)
{
- GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(actionType, this, targetLimb.character, targetLimb, null, item.WorldPosition));
- GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, targetLimb.character, targetLimb, null, item.WorldPosition));
+ server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, this, targetLimb.character, targetLimb, null, item.WorldPosition));
+ server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, targetLimb.character, targetLimb, null, item.WorldPosition));
}
-#endif
}
else
{
- ApplyStatusEffects(actionType, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
- ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
-#if SERVER
- if (GameMain.NetworkMember.IsServer)
+ ApplyStatusEffects(conditionalActionType, 1.0f, useTarget: target.Body.UserData as Entity, user: User);
+ ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: User);
+ if (GameMain.NetworkMember is { IsServer: true } server)
{
- GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(actionType, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
- GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
+ server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
+ server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
}
-#endif
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs
index 32f151379..1ccd035f9 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs
@@ -289,7 +289,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
Light.ParentSub = item.Submarine;
#endif
- if (item.Container != null)
+ if (item.Container != null && !(item.GetRootInventoryOwner() is Character))
{
SetLightSourceState(false, 0.0f);
return;
@@ -301,7 +301,7 @@ namespace Barotrauma.Items.Components
if (body != null && !body.Enabled)
{
SetLightSourceState(false, 0.0f);
- return;
+ return;
}
//currPowerConsumption = powerConsumption;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs
index 8902fd99e..a5020cff3 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs
@@ -138,7 +138,7 @@ namespace Barotrauma
private ConcurrentQueue impactQueue;
//a dictionary containing lists of the status effects in all the components of the item
- private readonly bool[] hasStatusEffectsOfType;
+ private readonly bool[] hasStatusEffectsOfType = new bool[Enum.GetValues(typeof(ActionType)).Length];
private readonly Dictionary> statusEffectLists;
public Dictionary SerializableProperties { get; protected set; }
@@ -630,7 +630,7 @@ namespace Barotrauma
{
if (!spawnedInCurrentOutpost && value)
{
- OriginalOutpost = GameMain.GameSession?.StartLocation?.BaseName ?? "";
+ OriginalOutpost = GameMain.GameSession?.LevelData?.Seed;
}
spawnedInCurrentOutpost = value;
}
@@ -651,7 +651,9 @@ namespace Barotrauma
set
{
originalOutpost = value;
- if (!string.IsNullOrEmpty(value) && GameMain.GameSession?.LevelData?.Type == LevelData.LevelType.Outpost && GameMain.GameSession?.StartLocation?.BaseName == value)
+ if (!string.IsNullOrEmpty(value) &&
+ GameMain.GameSession?.LevelData?.Type == LevelData.LevelType.Outpost &&
+ GameMain.GameSession?.LevelData?.Seed == value)
{
spawnedInCurrentOutpost = true;
}
@@ -1005,7 +1007,6 @@ namespace Barotrauma
}
}
- hasStatusEffectsOfType = new bool[Enum.GetValues(typeof(ActionType)).Length];
foreach (ItemComponent ic in components)
{
if (ic is Pickable pickable)
@@ -1654,7 +1655,7 @@ namespace Barotrauma
if (effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters) || effect.HasTargetType(StatusEffect.TargetType.NearbyItems))
{
- targets.AddRange(effect.GetNearbyTargets(WorldPosition, targets));
+ effect.AddNearbyTargets(WorldPosition, targets);
if (targets.Count > 0)
{
hasTargets = true;
@@ -2764,18 +2765,20 @@ namespace Barotrauma
if (!ic.HasRequiredContainedItems(user, addMessage: user == Character.Controlled)) { continue; }
bool success = Rand.Range(0.0f, 0.5f) < ic.DegreeOfSuccess(user);
- ActionType actionType = success ? ActionType.OnUse : ActionType.OnFailure;
+ ActionType conditionalActionType = success ? ActionType.OnSuccess : ActionType.OnFailure;
#if CLIENT
- ic.PlaySound(actionType, user);
+ ic.PlaySound(conditionalActionType, user);
+ ic.PlaySound(ActionType.OnUse, user);
#endif
ic.WasUsed = true;
- ic.ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: user);
+
+ ic.ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, user: user);
+ ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, character, targetLimb, user: user);
if (GameMain.NetworkMember is { IsServer: true })
{
- GameMain.NetworkMember.CreateEntityEvent(this, new ApplyStatusEffectEventData(
- actionType, ic, character, targetLimb));
+ GameMain.NetworkMember.CreateEntityEvent(this, new ApplyStatusEffectEventData(conditionalActionType, ic, character, targetLimb));
}
if (ic.DeleteOnUse) { remove = true; }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs
index 478b231fa..4dafd2d11 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs
@@ -814,8 +814,6 @@ namespace Barotrauma
? category
: MapEntityCategory.Misc;
- var parentType = ConfigElement.Parent?.GetAttributeIdentifier("itemtype", "");
-
//nameidentifier can be used to make multiple items use the same names and descriptions
Identifier nameIdentifier = ConfigElement.GetAttributeIdentifier("nameidentifier", "");
@@ -831,7 +829,7 @@ namespace Barotrauma
name = name.Fallback(OriginalName);
}
- if (parentType == "wrecked")
+ if (category == MapEntityCategory.Wrecked)
{
name = TextManager.GetWithVariable("wreckeditemformat", "[name]", name);
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs
index 26b7d6e00..5de6d463e 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
+using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
namespace Barotrauma
@@ -57,6 +58,20 @@ namespace Barotrauma
///
public int TargetSlot = -1;
+ ///
+ /// Overrides the position defined in ItemContainer.
+ ///
+ public Vector2? ItemPos;
+
+ ///
+ /// Only affects when ItemContainer.hideItems is false. Doesn't override the value.
+ ///
+ public bool? Hide;
+
+ public float Rotation;
+
+ public bool SetActive;
+
public string JoinedIdentifiers
{
get { return string.Join(",", Identifiers); }
@@ -202,7 +217,18 @@ namespace Barotrauma
new XAttribute("requireempty", RequireEmpty),
new XAttribute("excludefullcondition", ExcludeFullCondition),
new XAttribute("targetslot", TargetSlot),
- new XAttribute("allowvariants", AllowVariants));
+ new XAttribute("allowvariants", AllowVariants),
+ new XAttribute("rotation", Rotation),
+ new XAttribute("setactive", SetActive));
+
+ if (Hide.HasValue)
+ {
+ element.Add(new XAttribute(nameof(Hide), Hide.Value));
+ }
+ if (ItemPos.HasValue)
+ {
+ element.Add(new XAttribute(nameof(ItemPos), ItemPos.Value));
+ }
if (excludedIdentifiers.Count > 0)
{
@@ -267,8 +293,18 @@ namespace Barotrauma
ExcludeBroken = element.GetAttributeBool("excludebroken", true),
RequireEmpty = element.GetAttributeBool("requireempty", false),
ExcludeFullCondition = element.GetAttributeBool("excludefullcondition", false),
- AllowVariants = element.GetAttributeBool("allowvariants", true)
+ AllowVariants = element.GetAttributeBool("allowvariants", true),
+ Rotation = element.GetAttributeFloat("rotation", 0f),
+ SetActive = element.GetAttributeBool("setactive", false)
};
+ if (element.GetAttribute(nameof(Hide)) != null)
+ {
+ ri.Hide = element.GetAttributeBool(nameof(Hide), false);
+ }
+ if (element.GetAttribute(nameof(ItemPos)) != null)
+ {
+ ri.ItemPos = element.GetAttributeVector2(nameof(ItemPos), Vector2.Zero);
+ }
string typeStr = element.GetAttributeString("type", "");
if (string.IsNullOrEmpty(typeStr))
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Biome.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Biome.cs
index ee915e10e..1144fc52b 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Biome.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Biome.cs
@@ -1,3 +1,5 @@
+using Barotrauma.Extensions;
+using System.Collections.Generic;
using System.Collections.Immutable;
namespace Barotrauma
@@ -18,6 +20,14 @@ namespace Barotrauma
public readonly ImmutableHashSet AllowedZones;
+ private readonly SubmarineAvailability? submarineAvailability;
+ private readonly ImmutableHashSet submarineAvailabilityOverrides;
+
+ public readonly record struct SubmarineAvailability(
+ Identifier LocationType,
+ SubmarineClass Class = SubmarineClass.Undefined,
+ int MaxTier = 0);
+
public Biome(ContentXElement element, LevelGenerationParametersFile file) : base(file, ParseIdentifier(element))
{
OldIdentifier = element.GetAttributeIdentifier("oldidentifier", Identifier.Empty);
@@ -34,6 +44,26 @@ namespace Barotrauma
AllowedZones = element.GetAttributeIntArray("AllowedZones", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }).ToImmutableHashSet();
MinDifficulty = element.GetAttributeFloat("MinDifficulty", 0);
maxDifficulty = element.GetAttributeFloat("MaxDifficulty", 100);
+
+ var submarineAvailabilityOverrides = new HashSet();
+ if (element.GetChildElement("submarines") is ContentXElement availabilityElement)
+ {
+ submarineAvailability = GetAvailability(availabilityElement);
+ foreach (var overrideElement in availabilityElement.GetChildElements("override"))
+ {
+ var availabilityOverride = GetAvailability(overrideElement);
+ submarineAvailabilityOverrides.Add(availabilityOverride);
+ }
+ }
+ this.submarineAvailabilityOverrides = submarineAvailabilityOverrides.ToImmutableHashSet();
+
+ static SubmarineAvailability GetAvailability(ContentXElement element)
+ {
+ return new SubmarineAvailability(
+ LocationType: element.GetAttributeIdentifier("locationtype", Identifier.Empty),
+ Class: element.GetAttributeEnum("class", SubmarineClass.Undefined),
+ MaxTier: element.GetAttributeInt("maxtier", 0));
+ }
}
public static Identifier ParseIdentifier(ContentXElement element)
@@ -47,6 +77,31 @@ namespace Barotrauma
return identifier;
}
+ public int HighestSubmarineTierAvailable(SubmarineClass subClass, Identifier locationType)
+ {
+ if (!submarineAvailability.HasValue)
+ {
+ // If the availability is not explicitly defined, make all subs available
+ return SubmarineInfo.HighestTier;
+ }
+ int maxTier = submarineAvailability.Value.MaxTier;
+ if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == locationType && a.Class == subClass) is SubmarineAvailability locationAndClassOverride)
+ {
+ maxTier = locationAndClassOverride.MaxTier;
+ }
+ else if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == locationType && a.Class == SubmarineClass.Undefined) is SubmarineAvailability locationOverride)
+ {
+ maxTier = locationOverride.MaxTier;
+ }
+ else if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == Identifier.Empty && a.Class == subClass) is SubmarineAvailability classOverride)
+ {
+ maxTier = classOverride.MaxTier;
+ }
+ return maxTier;
+ }
+
+ public bool IsSubmarineAvailable(SubmarineInfo info, Identifier locationType) => info.Tier <= HighestSubmarineTierAvailable(info.SubmarineClass, locationType);
+
public override void Dispose() { }
}
}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs
index 433b711a2..46adf35e2 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs
@@ -447,7 +447,7 @@ namespace Barotrauma
private Level(LevelData levelData) : base(null, 0)
{
- this.LevelData = levelData;
+ LevelData = levelData;
borders = new Rectangle(Point.Zero, levelData.Size);
}
@@ -3939,7 +3939,7 @@ namespace Barotrauma
}
SubmarineInfo outpostInfo;
- Submarine outpost;
+ Submarine outpost = null;
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
{
if (OutpostGenerationParams.OutpostParams.Any() || LevelData.ForceOutpostGenerationParams != null)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs
index 4120c334a..8e9ca73d1 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs
@@ -59,6 +59,7 @@ namespace Barotrauma
public readonly List EventHistory = new List();
public readonly List NonRepeatableEvents = new List();
+ public readonly HashSet UsedUniqueSets = new HashSet();
public bool EventsExhausted { get; set; }
@@ -143,10 +144,11 @@ 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);
}
-
///
/// Instantiates level data using the properties of the connection (seed, size, difficulty)
///
@@ -284,6 +286,12 @@ namespace Barotrauma
newElement.Add(new XAttribute("nonrepeatableevents", string.Join(',', NonRepeatableEvents.Select(p => p.Identifier))));
}
}
+
+ if (UsedUniqueSets.Any())
+ {
+ newElement.Add(new XAttribute(nameof(UsedUniqueSets), string.Join(',', UsedUniqueSets)));
+ }
+
parentElement.Add(newElement);
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs
index a2ad86140..213fad7bf 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs
@@ -287,6 +287,13 @@ namespace Barotrauma
private set;
}
+ [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable]
+ public Color SpriteColor
+ {
+ get;
+ private set;
+ }
+
public string Name => Identifier.Value;
public List ChildObjects
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs
index 480163ffa..a5c1b3f85 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs
@@ -661,7 +661,7 @@ namespace Barotrauma
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
- targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
+ effect.AddNearbyTargets(worldPosition, targets);
effect.Apply(effect.type, deltaTime, triggerer, targets);
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs
index 80b11198b..a8426ed70 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs
@@ -61,7 +61,7 @@ namespace Barotrauma
private LocationType addInitialMissionsForType;
- public bool Discovered { get; private set; }
+ public bool Discovered => GameMain.GameSession?.Map?.IsDiscovered(this) ?? false;
public readonly Dictionary ProximityTimer = new Dictionary();
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
@@ -287,12 +287,12 @@ namespace Barotrauma
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
if (characters.Any())
{
- if (Location.Reputation?.Faction is { } faction && faction.IsAffiliated())
+ if (Location.Reputation?.Faction is { } faction && faction.GetPlayerAffiliationStatus() is FactionAffiliation.Affiliated)
{
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated));
}
- price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier));
- price *= 1f + characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplier, tag)));
+ price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
+ price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplier, tag)));
}
// Price should never go below 1 mk
return Math.Max((int)price, 1);
@@ -497,7 +497,6 @@ namespace Barotrauma
baseName = element.GetAttributeString("basename", "");
Name = element.GetAttributeString("name", "");
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
- Discovered = element.GetAttributeBool("discovered", false);
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
IsGateBetweenBiomes = element.GetAttributeBool("isgatebetweenbiomes", false);
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
@@ -1292,14 +1291,17 @@ namespace Barotrauma
return characters.Sum(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
}
- public void Discover(bool checkTalents = true)
+ public int HighestSubmarineTierAvailable(SubmarineClass submarineClass)
{
- if (Discovered) { return; }
- Discovered = true;
- if (checkTalents)
- {
- GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this)));
- }
+ if (!HasOutpost()) { return 0; }
+ return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier;
+ }
+
+ public int HighestSubmarineTierAvailable() => HighestSubmarineTierAvailable(SubmarineClass.Undefined);
+
+ public bool IsSubmarineAvailable(SubmarineInfo info)
+ {
+ return Biome?.IsSubmarineAvailable(info, Type.Identifier) ?? true;
}
public void Reset()
@@ -1313,7 +1315,6 @@ namespace Barotrauma
ClearMissions();
LevelData?.EventHistory?.Clear();
UnlockInitialMissions();
- Discovered = false;
}
public XElement Save(Map map, XElement parentElement)
@@ -1324,7 +1325,6 @@ namespace Barotrauma
new XAttribute("basename", BaseName),
new XAttribute("name", Name),
new XAttribute("biome", Biome?.Identifier.Value ?? string.Empty),
- new XAttribute("discovered", Discovered),
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
new XAttribute("pricemultiplier", PriceMultiplier),
new XAttribute("isgatebetweenbiomes", IsGateBetweenBiomes),
@@ -1453,7 +1453,7 @@ namespace Barotrauma
HireManager?.Remove();
}
- class AbilityLocation : AbilityObject, IAbilityLocation
+ public class AbilityLocation : AbilityObject, IAbilityLocation
{
public AbilityLocation(Location location)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs
index 97651462a..fdb6aad08 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs
@@ -24,6 +24,7 @@ namespace Barotrauma
public readonly Dictionary MinCountPerZone = new Dictionary();
public readonly LocalizedString Name;
+ public readonly LocalizedString Description;
public readonly float BeaconStationChance;
@@ -70,6 +71,13 @@ namespace Barotrauma
public Sprite Sprite { get; private set; }
public Sprite RadiationSprite { get; }
+ private readonly Identifier forceOutpostGenerationParamsIdentifier;
+
+ ///
+ /// If set to true, only event sets that explicitly define this location type in can be selected at this location. Defaults to false.
+ ///
+ public bool IgnoreGenericEvents { get; }
+
public Color SpriteColor
{
get;
@@ -96,6 +104,7 @@ namespace Barotrauma
public LocationType(ContentXElement element, LocationTypesFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName))
{
Name = TextManager.Get("LocationName." + Identifier, "unknown");
+ Description = TextManager.Get("LocationDescription." + Identifier, "");
BeaconStationChance = element.GetAttributeFloat("beaconstationchance", 0.0f);
@@ -110,6 +119,10 @@ namespace Barotrauma
ReplaceInRadiation = element.GetAttributeIdentifier(nameof(ReplaceInRadiation), Identifier.Empty);
+ forceOutpostGenerationParamsIdentifier = element.GetAttributeIdentifier("forceoutpostgenerationparams", Identifier.Empty);
+
+ IgnoreGenericEvents = element.GetAttributeBool(nameof(IgnoreGenericEvents), false);
+
string teamStr = element.GetAttributeString("outpostteam", "FriendlyNPC");
Enum.TryParse(teamStr, out OutpostTeam);
@@ -261,6 +274,15 @@ namespace Barotrauma
}
}
+ public OutpostGenerationParams GetForcedOutpostGenerationParams()
+ {
+ if (OutpostGenerationParams.OutpostParams.TryGet(forceOutpostGenerationParamsIdentifier, out var parameters))
+ {
+ return parameters;
+ }
+ return null;
+ }
+
public override void Dispose() { }
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs
index 3da878131..cdcfaedab 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs
@@ -68,10 +68,15 @@ namespace Barotrauma
public List Locations { get; private set; }
+ private readonly List locationsDiscovered = new List();
+ private readonly List outpostsVisited = new List();
+
public List Connections { get; private set; }
public Radiation Radiation;
+ private bool wasLocationDiscoveryOrderTracked = true;
+
public Map(CampaignSettings settings)
{
generationParams = MapGenerationParams.Instance;
@@ -282,7 +287,12 @@ namespace Barotrauma
}
}
- CurrentLocation.Discover(true);
+ if (campaign.IsSinglePlayer && campaign.Settings.TutorialEnabled && LocationType.Prefabs.TryGet("tutorialoutpost", out var tutorialOutpost))
+ {
+ CurrentLocation.ChangeType(tutorialOutpost);
+ }
+ Discover(CurrentLocation);
+ Visit(CurrentLocation);
CurrentLocation.CreateStores();
foreach (var location in Locations)
@@ -820,7 +830,8 @@ namespace Barotrauma
SelectedConnection.Passed = true;
CurrentLocation = SelectedLocation;
- CurrentLocation.Discover();
+ Discover(CurrentLocation);
+ Visit(CurrentLocation);
SelectedLocation = null;
CurrentLocation.CreateStores();
@@ -851,7 +862,7 @@ namespace Barotrauma
Location prevLocation = CurrentLocation;
CurrentLocation = Locations[index];
- CurrentLocation.Discover();
+ Discover(CurrentLocation);
CurrentLocation.CreateStores();
if (prevLocation != CurrentLocation)
@@ -1184,6 +1195,51 @@ namespace Barotrauma
partial void ClearAnimQueue();
+ public void Discover(Location location, bool checkTalents = true)
+ {
+ if (location is null) { return; }
+ if (locationsDiscovered.Contains(location)) { return; }
+ locationsDiscovered.Add(location);
+ if (checkTalents)
+ {
+ GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new Location.AbilityLocation(location)));
+ }
+ }
+
+ public void Visit(Location location)
+ {
+ if (location is null) { return; }
+ if (!location.HasOutpost()) { return; }
+ if (outpostsVisited.Contains(location)) { return; }
+ outpostsVisited.Add(location);
+ }
+
+ public void ClearLocationHistory()
+ {
+ locationsDiscovered.Clear();
+ outpostsVisited.Clear();
+ }
+
+ public int? GetDiscoveryIndex(Location location)
+ {
+ if (!wasLocationDiscoveryOrderTracked) { return null; }
+ if (location is null) { return -1; }
+ return locationsDiscovered.IndexOf(location);
+ }
+
+ public int? GetVisitIndex(Location location)
+ {
+ if (!wasLocationDiscoveryOrderTracked) { return null; }
+ if (location is null) { return -1; }
+ return outpostsVisited.IndexOf(location);
+ }
+
+ public bool IsDiscovered(Location location)
+ {
+ if (location is null) { return false; }
+ return locationsDiscovered.Contains(location);
+ }
+
///
/// Load a previously saved map from an xml element
///
@@ -1211,6 +1267,7 @@ namespace Barotrauma
return;
}
+ ClearLocationHistory();
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -1226,19 +1283,12 @@ namespace Barotrauma
}
}
location.LoadLocationTypeChange(subElement);
+
+ // Backwards compatibility
if (subElement.GetAttributeBool("discovered", false))
{
- location.Discover(checkTalents: false);
- }
- if (location.Discovered)
- {
-#if CLIENT
- RemoveFogOfWar(location);
-#endif
- if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
- {
- furthestDiscoveredLocation = location;
- }
+ Discover(location);
+ wasLocationDiscoveryOrderTracked = false;
}
Identifier locationType = subElement.GetAttributeIdentifier("type", Identifier.Empty);
@@ -1268,6 +1318,36 @@ namespace Barotrauma
case "radiation":
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
break;
+ case "discovered":
+ foreach (var childElement in subElement.GetChildElements("location"))
+ {
+ int index = childElement.GetAttributeInt("i", -1);
+ if (index < 0) { continue; }
+ if (Locations[index] is not Location l) { continue; }
+ Discover(l);
+ }
+ break;
+ case "visited":
+ foreach (var childElement in subElement.GetChildElements("location"))
+ {
+ int index = childElement.GetAttributeInt("i", -1);
+ if (index < 0) { continue; }
+ if (Locations[index] is not Location l) { continue; }
+ Visit(l);
+ }
+ break;
+ }
+ }
+
+ void Discover(Location location)
+ {
+ this.Discover(location, checkTalents: false);
+#if CLIENT
+ RemoveFogOfWar(location);
+#endif
+ if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
+ {
+ furthestDiscoveredLocation = location;
}
}
@@ -1343,6 +1423,30 @@ namespace Barotrauma
mapElement.Add(Radiation.Save());
}
+ if (locationsDiscovered.Any())
+ {
+ var discoveryElement = new XElement("discovered");
+ foreach (Location location in locationsDiscovered)
+ {
+ int index = Locations.IndexOf(location);
+ var locationElement = new XElement("location", new XAttribute("i", index));
+ discoveryElement.Add(locationElement);
+ }
+ mapElement.Add(discoveryElement);
+ }
+
+ if (outpostsVisited.Any())
+ {
+ var visitElement = new XElement("visited");
+ foreach (Location location in outpostsVisited)
+ {
+ int index = Locations.IndexOf(location);
+ var locationElement = new XElement("location", new XAttribute("i", index));
+ visitElement.Add(locationElement);
+ }
+ mapElement.Add(visitElement);
+ }
+
element.Add(mapElement);
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs
index 6e9b95be9..aa8082144 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs
@@ -96,6 +96,8 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes), Editable]
public string ReplaceInRadiation { get; set; }
+ public ContentPath OutpostFilePath { get; set; }
+
public class ModuleCount
{
public Identifier Identifier;
@@ -182,6 +184,7 @@ namespace Barotrauma
Name = element.GetAttributeString("name", Identifier.Value);
allowedLocationTypes = element.GetAttributeIdentifierArray("allowedlocationtypes", Array.Empty()).ToHashSet();
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
+ OutpostFilePath = element.GetAttributeContentPath(nameof(OutpostFilePath));
var humanPrefabCollections = new List>();
foreach (var subElement in element.Elements())
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs
index 30217e2a5..9c76e0bb2 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs
@@ -143,7 +143,7 @@ namespace Barotrauma
//select which module types the outpost should consist of
List pendingModuleFlags =
onlyEntrance ?
- generationParams.ModuleCounts.First().Identifier.ToEnumerable().ToList() :
+ (generationParams.ModuleCounts.FirstOrDefault()?.Identifier.ToEnumerable() ?? Enumerable.Empty()).ToList() :
SelectModules(outpostModules, generationParams);
foreach (Identifier flag in pendingModuleFlags)
@@ -246,6 +246,7 @@ namespace Barotrauma
var outpostFiles = ContentPackageManager.EnabledPackages.All
.SelectMany(p => p.GetFiles())
+ .Where(f => !TutorialPrefab.Prefabs.Any(tp => tp.OutpostPath == f.Path))
.OrderBy(f => f.UintIdentifier).ToArray();
if (!outpostFiles.Any())
{
@@ -696,6 +697,14 @@ namespace Barotrauma
rect.Location += (module.Offset + module.MoveOffset).ToPoint();
rect.Y += module.Bounds.Height;
+ Vector2? selfGapPos1 = null;
+ Vector2? selfGapPos2 = null;
+ if (module.PreviousModule != null)
+ {
+ selfGapPos1 = module.Offset + module.ThisGap.Position + module.MoveOffset;
+ selfGapPos2 = module.PreviousModule.Offset + module.PreviousGap.Position + module.PreviousModule.MoveOffset;
+ }
+
foreach (PlacedModule otherModule in modules)
{
if (otherModule == module || otherModule.PreviousModule == null || otherModule.PreviousModule == module) { continue; }
@@ -710,7 +719,17 @@ namespace Barotrauma
Vector2 gapPos1 = otherModule.Offset + otherModule.ThisGap.Position + gapEdgeOffset + otherModule.MoveOffset;
Vector2 gapPos2 = otherModule.PreviousModule.Offset + otherModule.PreviousGap.Position + gapEdgeOffset + otherModule.PreviousModule.MoveOffset;
- if (Submarine.RectContains(rect, gapPos1) || Submarine.RectContains(rect, gapPos2) || MathUtils.GetLineRectangleIntersection(gapPos1, gapPos2, rect, out _))
+ if (Submarine.RectContains(rect, gapPos1) ||
+ Submarine.RectContains(rect, gapPos2) ||
+ MathUtils.GetLineRectangleIntersection(gapPos1, gapPos2, rect, out _))
+ {
+ return true;
+ }
+
+ //check if the connection overlaps with this module's connection
+ if (selfGapPos1.HasValue && selfGapPos2.HasValue &&
+ !gapPos1.NearlyEquals(gapPos2) && !selfGapPos1.Value.NearlyEquals(selfGapPos2.Value) &&
+ MathUtils.LinesIntersect(gapPos1, gapPos2, selfGapPos1.Value, selfGapPos2.Value))
{
return true;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs
index 38cf694b1..532ce3957 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs
@@ -55,7 +55,7 @@ namespace Barotrauma
public PriceInfo(int price, bool canBeBought,
int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0, float buyingPriceMultiplier = 1f,
- bool displayNonEmpty = false, string storeIdentifier = null)
+ bool displayNonEmpty = false, bool requiresUnlock = false, string storeIdentifier = null)
{
Price = price;
CanBeBought = canBeBought;
@@ -67,6 +67,7 @@ namespace Barotrauma
CanBeSpecial = canBeSpecial;
DisplayNonEmpty = displayNonEmpty;
StoreIdentifier = new Identifier(storeIdentifier);
+ RequiresUnlock = requiresUnlock;
}
public static List CreatePriceInfos(XElement element, out PriceInfo defaultPrice)
@@ -81,6 +82,7 @@ namespace Barotrauma
float buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
bool displayNonEmpty = element.GetAttributeBool("displaynonempty", false);
bool soldByDefault = element.GetAttributeBool("sold", element.GetAttributeBool("soldbydefault", true));
+ bool requiresUnlock = element.GetAttributeBool("requiresunlock", false);
foreach (XElement childElement in element.GetChildElements("price"))
{
float priceMultiplier = childElement.GetAttributeFloat("multiplier", 1.0f);
@@ -94,26 +96,28 @@ namespace Barotrauma
}
string storeIdentifier = childElement.GetAttributeString("storeidentifier", backwardsCompatibleIdentifier);
// TODO: Add some error messages if we have defined the min or max amount while the item is not sold
- var priceInfo = new PriceInfo((int)(priceMultiplier * basePrice),
- sold,
- sold ? GetMinAmount(childElement, minAmount) : 0,
- sold ? GetMaxAmount(childElement, maxAmount) : 0,
- canBeSpecial,
- storeMinLevelDifficulty,
- storeBuyingMultiplier,
- displayNonEmpty,
- storeIdentifier);
+ var priceInfo = new PriceInfo(price: (int)(priceMultiplier * basePrice),
+ canBeBought: sold,
+ minAmount: sold ? GetMinAmount(childElement, minAmount) : 0,
+ maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0,
+ canBeSpecial: canBeSpecial,
+ minLevelDifficulty: storeMinLevelDifficulty,
+ buyingPriceMultiplier: storeBuyingMultiplier,
+ displayNonEmpty: displayNonEmpty,
+ requiresUnlock: requiresUnlock,
+ storeIdentifier: storeIdentifier);
priceInfos.Add(priceInfo);
}
bool soldElsewhere = soldByDefault && element.GetAttributeBool("soldelsewhere", element.GetAttributeBool("soldeverywhere", false));
- defaultPrice = new PriceInfo(basePrice,
- soldElsewhere,
- soldElsewhere ? minAmount : 0,
- soldElsewhere ? maxAmount : 0,
- canBeSpecial,
- minLevelDifficulty,
- buyingPriceMultiplier,
- displayNonEmpty);
+ defaultPrice = new PriceInfo(price: basePrice,
+ canBeBought: soldElsewhere,
+ minAmount: soldElsewhere ? minAmount : 0,
+ maxAmount: soldElsewhere ? maxAmount : 0,
+ canBeSpecial: canBeSpecial,
+ minLevelDifficulty: minLevelDifficulty,
+ buyingPriceMultiplier: buyingPriceMultiplier,
+ displayNonEmpty: displayNonEmpty,
+ requiresUnlock: requiresUnlock);
return priceInfos;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs
index 5ae404712..e2958efdf 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs
@@ -1114,7 +1114,8 @@ namespace Barotrauma
{
if (item.Submarine != this) { continue; }
var pump = item.GetComponent();
- if (pump == null || !item.HasTag("ballast") || item.CurrentHull == null) { continue; }
+ if (pump == null || item.CurrentHull == null) { continue; }
+ if (!item.HasTag("ballast") && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
pump.FlowPercentage = 0.0f;
ballastHulls.Add(item.CurrentHull);
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs
index 426941fe8..c40ed58c8 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs
@@ -314,6 +314,7 @@ namespace Barotrauma
Tier = original.Tier;
IsManuallyOutfitted = original.IsManuallyOutfitted;
Tags = original.Tags;
+ OutpostGenerationParams = original.OutpostGenerationParams;
if (original.OutpostModuleInfo != null)
{
OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo);
@@ -747,6 +748,8 @@ namespace Barotrauma
return doc;
}
- public static int GetDefaultTier(int price) => price > 20000 ? 3 : price > 10000 ? 2 : 1;
+ public static int GetDefaultTier(int price) => price > 20000 ? HighestTier : price > 10000 ? 2 : 1;
+
+ public const int HighestTier = 3;
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs
index 6e5300c4c..8bde51456 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs
@@ -27,7 +27,8 @@ namespace Barotrauma.Networking
SellSubItems = 0x4000,
ManageMap = 0x8000,
ManageHires = 0x10000,
- All = 0x1FFFF
+ ManageBotTalents = 0x20000,
+ All = 0x3FFFF
}
class PermissionPreset
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs
index cbf0474b8..e4cbc1bb7 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs
@@ -382,12 +382,17 @@ namespace Barotrauma.Networking
private bool autoRestart;
- public bool IsPublic;
-
private int maxPlayers;
public List ClientPermissions { get; private set; } = new List();
+ [Serialize(true, IsPropertySaveable.Yes)]
+ public bool IsPublic
+ {
+ get;
+ set;
+ }
+
private int tickRate = 20;
[Serialize(20, IsPropertySaveable.Yes)]
public int TickRate
@@ -518,13 +523,20 @@ namespace Barotrauma.Networking
}
}
- [Serialize(Barotrauma.LosMode.Opaque, IsPropertySaveable.Yes)]
+ [Serialize(LosMode.Opaque, IsPropertySaveable.Yes)]
public LosMode LosMode
{
get;
set;
}
+ [Serialize(EnemyHealthBarMode.ShowAll, IsPropertySaveable.Yes)]
+ public EnemyHealthBarMode ShowEnemyHealthBars
+ {
+ get;
+ set;
+ }
+
[Serialize(800, IsPropertySaveable.Yes)]
public int LinesPerLogFile
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs
index e1ec4e598..b327c3903 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs
@@ -35,10 +35,19 @@ namespace Barotrauma
Activity
}
+ public enum EnemyHealthBarMode
+ {
+ ShowAll,
+ BossHealthBarsOnly,
+ HideAll
+ }
+
public static class GameSettings
{
public struct Config
{
+ public const float DefaultAimAssist = 0.05f;
+
public static Config GetDefault()
{
Config config = new Config
@@ -50,7 +59,8 @@ namespace Barotrauma
SubEditorBackground = new Color(13, 37, 69, 255),
EnableSplashScreen = true,
PauseOnFocusLost = true,
- AimAssistAmount = 0.5f,
+ AimAssistAmount = DefaultAimAssist,
+ ShowEnemyHealthBars = EnemyHealthBarMode.ShowAll,
EnableMouseLook = true,
ChatOpen = true,
CrewMenuOpen = true,
@@ -110,6 +120,7 @@ namespace Barotrauma
public LanguageIdentifier Language;
public bool VerboseLogging;
public bool SaveDebugConsoleLogs;
+ public string SavePath;
public int SubEditorUndoBuffer;
public int MaxAutoSaves;
public int AutoSaveIntervalSeconds;
@@ -118,6 +129,7 @@ namespace Barotrauma
public bool PauseOnFocusLost;
public float AimAssistAmount;
public bool EnableMouseLook;
+ public EnemyHealthBarMode ShowEnemyHealthBars;
public bool ChatOpen;
public bool CrewMenuOpen;
public bool EditorDisclaimerShown;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs
index f86226e55..b831c7670 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs
@@ -24,7 +24,8 @@ namespace Barotrauma
HasSpecifierTag,
Affliction,
EntityType,
- LimbType
+ LimbType,
+ SkillRequirement
}
public enum Comparison
@@ -73,6 +74,7 @@ namespace Barotrauma
case "targetcontainer":
case "targetgrandparent":
case "targetcontaineditem":
+ case "skillrequirement":
return false;
default:
return true;
@@ -110,6 +112,11 @@ namespace Barotrauma
{
Type = ConditionType.Uncertain;
}
+
+ if (attribute.Parent.GetAttributeBool("skillrequirement", false))
+ {
+ Type = ConditionType.SkillRequirement;
+ }
AttributeValue = valueString;
SplitAttributeValue = valueString.Split(',');
@@ -305,25 +312,20 @@ namespace Barotrauma
if (health == null) { return false; }
var affliction = health.GetAffliction(AttributeName.ToIdentifier());
float afflictionStrength = affliction == null ? 0.0f : affliction.Strength;
- if (FloatValue.HasValue)
- {
- float value = FloatValue.Value;
- switch (Operator)
- {
- case OperatorType.Equals:
- return afflictionStrength == value;
- case OperatorType.GreaterThan:
- return afflictionStrength > value;
- case OperatorType.GreaterThanEquals:
- return afflictionStrength >= value;
- case OperatorType.LessThan:
- return afflictionStrength < value;
- case OperatorType.LessThanEquals:
- return afflictionStrength <= value;
- case OperatorType.NotEquals:
- return afflictionStrength != value;
- }
- }
+
+ return ValueMatchesRequirement(afflictionStrength);
+ }
+ }
+ return false;
+ case ConditionType.SkillRequirement:
+ {
+ if (target == null) { return Operator == OperatorType.NotEquals; }
+
+ if (target is Character targetChar)
+ {
+ float skillLevel = targetChar.GetSkillLevel(AttributeName.ToIdentifier());
+
+ return ValueMatchesRequirement(skillLevel);
}
}
return false;
@@ -332,6 +334,30 @@ namespace Barotrauma
}
}
+ private bool ValueMatchesRequirement(float testedValue)
+ {
+ if (FloatValue.HasValue)
+ {
+ float value = FloatValue.Value;
+ switch (Operator)
+ {
+ case OperatorType.Equals:
+ return testedValue == value;
+ case OperatorType.GreaterThan:
+ return testedValue > value;
+ case OperatorType.GreaterThanEquals:
+ return testedValue >= value;
+ case OperatorType.LessThan:
+ return testedValue < value;
+ case OperatorType.LessThanEquals:
+ return testedValue <= value;
+ case OperatorType.NotEquals:
+ return testedValue != value;
+ }
+ }
+ return false;
+ }
+
private bool MatchesTagCondition(ISerializableEntity target)
{
if (!(target is Item item)) { return Operator == OperatorType.NotEquals; }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs
index 3173558d7..581a5ccc5 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs
@@ -877,10 +877,9 @@ namespace Barotrauma
return true;
}
- public IReadOnlyList GetNearbyTargets(Vector2 worldPosition, List targets = null)
+ public void AddNearbyTargets(Vector2 worldPosition, List targets)
{
- targets ??= new List();
- if (Range <= 0.0f) { return targets; }
+ if (Range <= 0.0f) { return; }
if (HasTargetType(TargetType.NearbyCharacters))
{
foreach (Character c in Character.CharacterList)
@@ -917,7 +916,6 @@ namespace Barotrauma
}
}
}
- return targets;
bool CheckDistance(ISpatialEntity e)
{
@@ -1362,7 +1360,7 @@ namespace Barotrauma
{
if (breakLimb)
{
- targetLimb.character.TrySeverLimbJoints(targetLimb, severLimbsProbability: 1, damage: -1, allowBeheading: true, attacker: user);
+ targetLimb.character.TrySeverLimbJoints(targetLimb, severLimbsProbability: 1, damage: -1, allowBeheading: true, ignoreSeveranceProbabilityModifier: true, attacker: user);
}
if (hideLimb)
{
@@ -2163,7 +2161,7 @@ namespace Barotrauma
if (result.Afflictions != null && result.Afflictions.Any(a => a.Prefab == limbAffliction.Prefab) &&
(!affliction.Prefab.LimbSpecific || limb.character.CharacterHealth.GetAfflictionLimb(affliction) == limb))
{
- if (type == ActionType.OnUse)
+ if (type == ActionType.OnUse || type == ActionType.OnSuccess)
{
limbAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs b/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs
index b4aa8a883..d83ebf7af 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs
@@ -310,7 +310,7 @@ namespace Barotrauma.Steam
+ "unexpected deletion of your hard work.\n"
+ "Instead, modify a copy of your mod in LocalMods.\n";
- string workshopModDirReadmeLocation = Path.Combine(SaveUtil.SaveFolder, "WorkshopMods", "README.txt");
+ string workshopModDirReadmeLocation = Path.Combine(SaveUtil.DefaultSaveFolder, "WorkshopMods", "README.txt");
if (!File.Exists(workshopModDirReadmeLocation))
{
Directory.CreateDirectory(Path.GetDirectoryName(workshopModDirReadmeLocation)!);
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs
index 1aa43ae28..84b97b6b0 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs
@@ -108,6 +108,8 @@ namespace Barotrauma
public readonly bool IsWallUpgrade;
public readonly LocalizedString Name;
+ private readonly object mutex = new object();
+
public readonly IEnumerable