v1.4.4.1 (Blood in the Water Update)

This commit is contained in:
Regalis11
2024-04-24 18:09:05 +03:00
parent 89b91d1c3e
commit ff1b8951a7
397 changed files with 15250 additions and 6479 deletions
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml.Linq;
using System.Linq;
using Barotrauma.IO;
@@ -13,15 +14,31 @@ using Barotrauma.SpriteDeformations;
namespace Barotrauma
{
public enum CanEnterSubmarine
{
/// <summary>
/// No part of the ragdoll can go inside a submarine
/// </summary>
False,
/// <summary>
/// Can fully enter a submarine. Make sure to only allow this on small/medium sized creatures that can reasonably fit inside rooms.
/// </summary>
True,
/// <summary>
/// The ragdoll's limbs can enter the sub, but the collider can't.
/// Can be used to e.g. allow the monster's head to poke into the sub to bite characters, even if the whole monster can't fit in the sub.
/// </summary>
Partial
}
class HumanRagdollParams : RagdollParams
{
public static HumanRagdollParams GetRagdollParams(Identifier speciesName, string fileName = null) => GetRagdollParams<HumanRagdollParams>(speciesName, fileName);
public static HumanRagdollParams GetDefaultRagdollParams(Identifier speciesName) => GetDefaultRagdollParams<HumanRagdollParams>(speciesName);
public static HumanRagdollParams GetDefaultRagdollParams(Character character) => GetDefaultRagdollParams<HumanRagdollParams>(character);
}
class FishRagdollParams : RagdollParams
{
public static FishRagdollParams GetDefaultRagdollParams(Identifier speciesName) => GetDefaultRagdollParams<FishRagdollParams>(speciesName);
public static FishRagdollParams GetDefaultRagdollParams(Character character) => GetDefaultRagdollParams<FishRagdollParams>(character);
}
class RagdollParams : EditableParams, IMemorizable<RagdollParams>
@@ -37,8 +54,11 @@ namespace Barotrauma
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable()]
public Color Color { get; set; }
[Serialize(0.0f, IsPropertySaveable.Yes, description: "The orientation of the sprites as drawn on the sprite sheet. Can be overridden by setting a value for Limb's 'Sprite Orientation'."), Editable(-360, 360)]
[Serialize(0.0f, IsPropertySaveable.Yes, description: "General orientation of the sprites as drawn on the spritesheet. " +
"Defines the \"forward direction\" of the sprites. Should be configured as the direction pointing outwards from the main limb. " +
"Incorrectly defined orientations may lead to limbs being rotated incorrectly when e.g. when the character aims or flips to face a different direction. " +
"Can be overridden per sprite by setting a value for Limb's 'Sprite Orientation'."), Editable(-360, 360)]
public float SpritesheetOrientation { get; set; }
public bool IsSpritesheetOrientationHorizontal
@@ -53,11 +73,19 @@ namespace Barotrauma
private float limbScale;
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)]
public float LimbScale { get { return limbScale; } set { limbScale = MathHelper.Clamp(value, MIN_SCALE, MAX_SCALE); } }
public float LimbScale
{
get { return limbScale; }
set { limbScale = MathHelper.Clamp(value, MIN_SCALE, MAX_SCALE); }
}
private float jointScale;
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)]
public float JointScale { get { return jointScale; } set { jointScale = MathHelper.Clamp(value, MIN_SCALE, MAX_SCALE); } }
public float JointScale
{
get { return jointScale; }
set { jointScale = MathHelper.Clamp(value, MIN_SCALE, MAX_SCALE); }
}
// Don't show in the editor, because shouldn't be edited in runtime. Requires that the limb scale and the collider sizes are adjusted. TODO: automatize?
[Serialize(1f, IsPropertySaveable.No)]
@@ -69,8 +97,8 @@ namespace Barotrauma
[Serialize(50f, IsPropertySaveable.Yes, description: "How much impact is required before the character takes impact damage?"), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
public float ImpactTolerance { get; set; }
[Serialize(true, IsPropertySaveable.Yes, description: "Can the creature enter submarine. Creatures that cannot enter submarines, always collide with it, even when there is a gap."), Editable()]
public bool CanEnterSubmarine { get; set; }
[Serialize(CanEnterSubmarine.True, IsPropertySaveable.Yes, description: "Can the creature enter submarine. Creatures that cannot enter submarines, always collide with it, even when there is a gap."), Editable()]
public CanEnterSubmarine CanEnterSubmarine { get; set; }
[Serialize(true, IsPropertySaveable.Yes), Editable]
public bool CanWalk { get; set; }
@@ -86,7 +114,7 @@ namespace Barotrauma
/// key2: File path
/// value: Ragdoll parameters
/// </summary>
private readonly static Dictionary<Identifier, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<Identifier, Dictionary<string, RagdollParams>>();
private static readonly Dictionary<Identifier, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<Identifier, Dictionary<string, RagdollParams>>();
public List<ColliderParams> Colliders { get; private set; } = new List<ColliderParams>();
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
@@ -106,8 +134,7 @@ namespace Barotrauma
CharacterPrefab prefab = CharacterPrefab.Find(p => p.Identifier == speciesName && (contentPackage == null || p.ContentFile.ContentPackage == contentPackage));
if (prefab?.ConfigElement == null)
{
DebugConsole.ThrowError($"Failed to find config file for '{speciesName}'",
contentPackage: contentPackage);
DebugConsole.ThrowError($"Failed to find config file for '{speciesName}'", contentPackage: contentPackage);
return string.Empty;
}
return GetFolder(prefab.ConfigElement, prefab.ContentFile.Path.Value);
@@ -115,99 +142,151 @@ namespace Barotrauma
private static string GetFolder(ContentXElement root, string filePath)
{
var folder = root?.GetChildElement("ragdolls")?.GetAttributeContentPath("folder")?.Value;
Debug.Assert(filePath != null);
Debug.Assert(root != null);
string folder = (root.GetChildElement("ragdolls") ?? root.GetChildElement("ragdoll"))?.GetAttributeContentPath("folder")?.Value;
if (folder.IsNullOrEmpty() || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
{
folder = IO.Path.Combine(IO.Path.GetDirectoryName(filePath), "Ragdolls") + IO.Path.DirectorySeparatorChar;
}
return folder.CleanUpPathCrossPlatform(correctFilenameCase: true);
}
public static T GetDefaultRagdollParams<T>(Identifier speciesName) where T : RagdollParams, new() => GetRagdollParams<T>(speciesName);
/// <summary>
/// If the file name is left null, default file is selected. If fails, will select the default file. Note: Use the filename without the extensions, don't use the full path!
/// If a custom folder is used, it's defined in the character info file.
/// </summary>
public static T GetRagdollParams<T>(Identifier speciesName, string fileName = null) where T : RagdollParams, new()
public static T GetDefaultRagdollParams<T>(Character character) where T : RagdollParams, new() => GetDefaultRagdollParams<T>(character.SpeciesName, character.Params, character.Prefab.ContentPackage);
public static T GetDefaultRagdollParams<T>(Identifier speciesName, CharacterParams characterParams, ContentPackage contentPackage) where T : RagdollParams, new()
{
if (speciesName.IsEmpty)
XElement mainElement = characterParams.VariantFile?.Root ?? characterParams.MainElement;
return GetDefaultRagdollParams<T>(speciesName, mainElement, contentPackage);
}
public static T GetDefaultRagdollParams<T>(Identifier speciesName, XElement characterRootElement, ContentPackage contentPackage) where T : RagdollParams, new()
{
Debug.Assert(contentPackage != null);
if (characterRootElement.IsOverride())
{
throw new Exception($"Species name null or empty!");
characterRootElement = characterRootElement.FirstElement();
}
Identifier ragdollSpecies = speciesName;
Identifier variantOf = characterRootElement.VariantOf();
if (characterRootElement != null && (characterRootElement.GetChildElement("ragdolls") ?? characterRootElement.GetChildElement("ragdoll")) is XElement ragdollElement)
{
if ((ragdollElement.GetAttributeContentPath("path", contentPackage) ?? ragdollElement.GetAttributeContentPath("file", contentPackage)) is ContentPath path)
{
return GetRagdollParams<T>(speciesName, ragdollSpecies, file: path, contentPackage);
}
else if (!variantOf.IsEmpty)
{
string folder = ragdollElement.GetAttributeContentPath("folder", contentPackage)?.Value;
if (folder.IsNullOrEmpty() || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
{
// Folder attribute not defined or set to default -> use the ragdoll defined in the base definition file.
if (CharacterPrefab.FindBySpeciesName(variantOf) is CharacterPrefab prefab)
{
ragdollSpecies = prefab.GetBaseCharacterSpeciesName(variantOf);
}
}
}
}
else if (!variantOf.IsEmpty && CharacterPrefab.FindBySpeciesName(variantOf) is CharacterPrefab prefab)
{
// Ragdoll element not defined -> use the ragdoll defined in the base definition file.
ragdollSpecies = prefab.GetBaseCharacterSpeciesName(variantOf);
}
// Using a null file definition means we use the default animations found in the Ragdolls folder.
return GetRagdollParams<T>(speciesName, ragdollSpecies, file: null, contentPackage);
}
public static T GetRagdollParams<T>(Identifier speciesName, Identifier ragdollSpecies, Either<string, ContentPath> file, ContentPackage contentPackage) where T : RagdollParams, new()
{
Debug.Assert(!speciesName.IsEmpty);
Debug.Assert(!ragdollSpecies.IsEmpty);
ContentPath contentPath = null;
string fileName = null;
if (file != null)
{
if (!file.TryGet(out fileName))
{
file.TryGet(out contentPath);
}
Debug.Assert(!fileName.IsNullOrWhiteSpace() || !contentPath.IsNullOrWhiteSpace());
}
Debug.Assert(contentPackage != null);
if (!allRagdolls.TryGetValue(speciesName, out Dictionary<string, RagdollParams> ragdolls))
{
ragdolls = new Dictionary<string, RagdollParams>();
allRagdolls.Add(speciesName, ragdolls);
}
if (!string.IsNullOrEmpty(fileName) && ragdolls.TryGetValue(fileName, out RagdollParams ragdoll))
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(ragdollSpecies);
if (ragdolls.TryGetValue(key, out RagdollParams ragdoll))
{
// Already cached.
return (T)ragdoll;
}
string selectedFile = null;
Identifier ragdollSpecies = speciesName;
if (CharacterPrefab.Prefabs.TryGet(speciesName, out var prefab))
if (!contentPath.IsNullOrEmpty())
{
if (!prefab.VariantOf.IsEmpty)
// Load the ragdoll from path.
T ragdollInstance = new T();
if (ragdollInstance.Load(contentPath, ragdollSpecies))
{
ragdollSpecies = prefab.VariantOf;
ragdolls.TryAdd(contentPath.Value, ragdollInstance);
return ragdollInstance;
}
string error = null;
string folder = GetFolder(ragdollSpecies);
if (!Directory.Exists(folder))
else
{
error = $"[RagdollParams] Invalid directory: {folder}. Using the default ragdoll.";
DebugConsole.ThrowError($"[AnimationParams] Failed to load an animation {ragdollInstance} from {contentPath.Value} for the character {speciesName}. Using the default ragdoll.", contentPackage: contentPackage);
}
}
// Seek the default ragdoll from the character's ragdoll folder.
string selectedFile;
string folder = GetFolder(ragdollSpecies);
if (Directory.Exists(folder))
{
var files = Directory.GetFiles(folder).OrderBy(f => f, StringComparer.OrdinalIgnoreCase);
if (files.None())
{
DebugConsole.ThrowError($"[RagdollParams] Could not find any ragdoll files from the folder: {folder}. Using the default ragdoll.", contentPackage: contentPackage);
selectedFile = GetDefaultFile(ragdollSpecies);
}
else
{
string[] files = Directory.GetFiles(folder);
if (files.None())
if (string.IsNullOrEmpty(fileName))
{
error = $"[RagdollParams] Could not find any ragdoll files from the folder: {folder}. Using the default ragdoll.";
selectedFile = GetDefaultFile(ragdollSpecies);
}
else if (string.IsNullOrEmpty(fileName))
{
// Files found, but none specified
selectedFile = GetDefaultFile(ragdollSpecies);
// Files found, but none specified -> Get a matching ragdoll from the specified folder.
// First try to find a file that matches the default file name. If that fails, just take any file.
string defaultFileName = GetDefaultFileName(ragdollSpecies);
selectedFile = files.FirstOrDefault(f => f.Contains(defaultFileName, StringComparison.OrdinalIgnoreCase)) ?? files.First();
}
else
{
selectedFile = files.FirstOrDefault(f => IO.Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
if (selectedFile == null)
{
error = $"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.";
DebugConsole.ThrowError($"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.", contentPackage: contentPackage);
selectedFile = GetDefaultFile(ragdollSpecies);
}
}
}
}
if (error != null)
{
DebugConsole.ThrowError(error,
contentPackage: prefab?.ContentPackage);
}
}
if (selectedFile == null)
{
throw new Exception("[RagdollParams] Selected file null!");
}
DebugConsole.Log($"[RagdollParams] Loading ragdoll from {selectedFile}.");
var characterPrefab = CharacterPrefab.Prefabs[speciesName];
T r = new T();
if (r.Load(ContentPath.FromRaw(characterPrefab.ContentPackage, selectedFile), ragdollSpecies))
{
if (!ragdolls.ContainsKey(r.Name))
{
ragdolls.Add(r.Name, r);
}
return r;
}
else
{
// Failing to create a ragdoll causes so many issues that cannot be handled. Dummy ragdoll just seems to make things harded to debug. It's better to fail early.
DebugConsole.ThrowError($"[RagdollParams] Invalid directory: {folder}. Using the default ragdoll.", contentPackage: contentPackage);
selectedFile = GetDefaultFile(ragdollSpecies);
}
Debug.Assert(selectedFile != null);
DebugConsole.Log($"[RagdollParams] Loading the ragdoll from {selectedFile}.");
T r = new T();
if (r.Load(ContentPath.FromRaw(contentPackage, selectedFile), speciesName))
{
ragdolls.TryAdd(key, r);
}
else
{
// Failing to create a ragdoll causes so many issues that cannot be handled. Dummy ragdoll just seems to make things harder to debug. It's better to fail early.
throw new Exception($"[RagdollParams] Failed to load ragdoll {r.Name} from {selectedFile} for the character {speciesName}.");
}
return r;
}
/// <summary>
@@ -234,9 +313,9 @@ namespace Barotrauma
instance.IsLoaded = instance.Deserialize(mainElement);
instance.Save();
instance.Load(contentPath, speciesName);
ragdolls.Add(instance.Name, instance);
ragdolls.Add(instance.FileNameWithoutExtension, instance);
DebugConsole.NewMessage("[RagdollParams] New default ragdoll params successfully created at " + fullPath, Color.NavajoWhite);
return instance as T;
return instance;
}
public static void ClearCache() => allRagdolls.Clear();
@@ -250,16 +329,17 @@ namespace Barotrauma
else
{
// Update the key by removing and re-adding the ragdoll.
string fileName = FileNameWithoutExtension;
if (allRagdolls.TryGetValue(SpeciesName, out Dictionary<string, RagdollParams> ragdolls))
{
ragdolls.Remove(Name);
ragdolls.Remove(fileName);
}
base.UpdatePath(fullPath);
if (ragdolls != null)
{
if (!ragdolls.ContainsKey(Name))
if (!ragdolls.ContainsKey(fileName))
{
ragdolls.Add(Name, this);
ragdolls.Add(fileName, this);
}
}
}
@@ -282,6 +362,7 @@ namespace Barotrauma
{
if (Load(file))
{
isVariantScaleApplied = false;
SpeciesName = speciesName;
CreateColliders();
CreateLimbs();
@@ -398,18 +479,21 @@ namespace Barotrauma
}
#endif
private bool variantScaleApplied;
public void ApplyVariantScale(XDocument variantFile)
private bool isVariantScaleApplied;
public void TryApplyVariantScale(XDocument variantFile)
{
if (variantScaleApplied) { return; }
if (isVariantScaleApplied) { return; }
if (variantFile == null) { return; }
var scaleMultiplier = variantFile.Root.GetChildElement("ragdoll")?.GetAttributeFloat("scalemultiplier", 1f);
if (scaleMultiplier.HasValue)
if (variantFile.GetRootExcludingOverride() is XElement root)
{
JointScale *= scaleMultiplier.Value;
LimbScale *= scaleMultiplier.Value;
if ((root.GetChildElement("ragdoll") ?? root.GetChildElement("ragdolls")) is XElement ragdollElement)
{
float scaleMultiplier = ragdollElement.GetAttributeFloat("scalemultiplier", 1f);
JointScale *= scaleMultiplier;
LimbScale *= scaleMultiplier;
}
}
variantScaleApplied = true;
isVariantScaleApplied = true;
}
#endregion
@@ -623,8 +707,11 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.Yes, description: "Disable drawing for this limb."), Editable()]
public bool Hide { get; set; }
[Serialize(float.NaN, IsPropertySaveable.Yes, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)]
[Serialize(float.NaN, IsPropertySaveable.Yes, description: "Orientation of the sprite as drawn on the spritesheet. " +
"Defines the \"forward direction\" of the sprite. Should be configured as the direction pointing outwards from the main limb." +
"Incorrectly defined orientations may lead to limbs being rotated incorrectly when e.g. when the character aims or flips to face a different direction. " +
"Overrides the value of 'Spritesheet Orientation' for this limb."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)]
public float SpriteOrientation { get; set; }
[Serialize(LimbType.None, IsPropertySaveable.Yes, description: "If set, the limb sprite will use the same sprite depth as the specified limb. Generally only useful for limbs that get added on the ragdoll on the fly (e.g. extra limbs added via gene splicing).")]
@@ -744,6 +831,9 @@ namespace Barotrauma
[Serialize(0.05f, IsPropertySaveable.Yes)]
public float Restitution { get; set; }
[Serialize(true, IsPropertySaveable.Yes, description: "Can the limb enter submarines? Only valid if the ragdoll's CanEnterSubmarine is set to Partial, otherwise the limb can enter if the ragdoll can."), Editable]
public bool CanEnterSubmarine { get; private set; }
public LimbParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
{
var spriteElement = element.GetChildElement("sprite");