v1.4.4.1 (Blood in the Water Update)
This commit is contained in:
+183
-149
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Barotrauma.IO;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
@@ -65,22 +66,19 @@ namespace Barotrauma
|
||||
abstract class AnimationParams : EditableParams, IMemorizable<AnimationParams>
|
||||
{
|
||||
public Identifier SpeciesName { get; private set; }
|
||||
public bool IsGroundedAnimation => AnimationType == AnimationType.Walk || AnimationType == AnimationType.Run || AnimationType == AnimationType.Crouch;
|
||||
public bool IsSwimAnimation => AnimationType == AnimationType.SwimSlow || AnimationType == AnimationType.SwimFast;
|
||||
public bool IsGroundedAnimation => AnimationType is AnimationType.Walk or AnimationType.Run or AnimationType.Crouch;
|
||||
public bool IsSwimAnimation => AnimationType is AnimationType.SwimSlow or AnimationType.SwimFast;
|
||||
|
||||
protected static Dictionary<Identifier, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<Identifier, Dictionary<string, AnimationParams>>();
|
||||
/// allAnimations[speciesName][fileName]
|
||||
/// <summary>
|
||||
/// The cached animations of all the characters that have been loaded.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Identifier, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<Identifier, Dictionary<string, AnimationParams>>();
|
||||
|
||||
private float _movementSpeed;
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = Ragdoll.MAX_SPEED, ValueStep = 0.1f)]
|
||||
public float MovementSpeed
|
||||
{
|
||||
get => _movementSpeed;
|
||||
set => _movementSpeed = value;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "The speed of the \"animation cycle\", i.e. how fast the character takes steps or moves the tail/legs/arms (the outcome depends what the clip is about)"),
|
||||
Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2, ValueStep = 0.01f)]
|
||||
public float MovementSpeed { get; set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "The speed of the \"animation cycle\", i.e. how fast the character takes steps or moves the tail/legs/arms (the outcome depends what the clip is about)"),
|
||||
Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2, ValueStep = 0.01f)]
|
||||
public float CycleSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -152,169 +150,214 @@ namespace Barotrauma
|
||||
|
||||
private static string GetFolder(ContentXElement root, string filePath)
|
||||
{
|
||||
var folder = root?.GetChildElement("animations")?.GetAttributeContentPath("folder")?.Value;
|
||||
Debug.Assert(filePath != null);
|
||||
Debug.Assert(root != null);
|
||||
string folder = root.GetChildElement("animations")?.GetAttributeContentPath("folder")?.Value;
|
||||
if (string.IsNullOrEmpty(folder) || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
folder = IO.Path.Combine(IO.Path.GetDirectoryName(filePath), "Animations");
|
||||
}
|
||||
return folder.CleanUpPathCrossPlatform(true);
|
||||
return folder.CleanUpPathCrossPlatform(correctFilenameCase: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a random filepath from multiple paths, matching the specified animation type.
|
||||
/// Selects all file paths that match the specified animation type and filters them alphabetically.
|
||||
/// </summary>
|
||||
public static string GetRandomFilePath(IReadOnlyList<string> filePaths, AnimationType type)
|
||||
public static IEnumerable<string> FilterAndSortFiles(IEnumerable<string> filePaths, AnimationType type)
|
||||
{
|
||||
return filePaths.GetRandom(f => AnimationPredicate(f, type), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects all file paths that match the specified animation type.
|
||||
/// </summary>
|
||||
public static IEnumerable<string> FilterFilesByType(IEnumerable<string> filePaths, AnimationType type)
|
||||
{
|
||||
return filePaths.Where(f => AnimationPredicate(f, type));
|
||||
}
|
||||
|
||||
private static bool AnimationPredicate(string filePath, AnimationType type)
|
||||
{
|
||||
var doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null) { return false; }
|
||||
var typeString = doc.Root.GetAttributeString("animationtype", null);
|
||||
if (string.IsNullOrWhiteSpace(typeString))
|
||||
return filePaths.Where(f => AnimationPredicate(f, type)).OrderBy(f => f, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
static bool AnimationPredicate(string filePath, AnimationType type)
|
||||
{
|
||||
typeString = doc.Root.GetAttributeString("AnimationType", "NotDefined");
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null) { return false; }
|
||||
return doc.GetRootExcludingOverride().GetAttributeEnum("animationtype", AnimationType.NotDefined) == type;
|
||||
}
|
||||
return Enum.TryParse(typeString, out AnimationType fileType) && fileType == type;
|
||||
}
|
||||
|
||||
public static T GetDefaultAnimParams<T>(Character character, AnimationType animType) where T : AnimationParams, new()
|
||||
protected static T GetDefaultAnimParams<T>(Character character, AnimationType animType) where T : AnimationParams, new()
|
||||
{
|
||||
// Using a null file definition means we are taking a first matching file from the folder.
|
||||
return GetAnimParams<T>(character, animType, file: null, throwErrors: true);
|
||||
}
|
||||
|
||||
protected static T GetAnimParams<T>(Character character, AnimationType animType, Either<string, ContentPath> file, bool throwErrors = true) where T : AnimationParams, new()
|
||||
{
|
||||
Identifier speciesName = character.SpeciesName;
|
||||
if (!character.VariantOf.IsEmpty
|
||||
&& (character.Params.VariantFile?.Root?.GetChildElement("animations")?.GetAttributeStringUnrestricted("folder", null)).IsNullOrEmpty())
|
||||
Identifier animSpecies = speciesName;
|
||||
if (!character.VariantOf.IsEmpty)
|
||||
{
|
||||
// Use the base animations defined in the base definition file.
|
||||
speciesName = character.VariantOf;
|
||||
}
|
||||
return GetAnimParams<T>(speciesName, animType, GetDefaultFileName(speciesName, animType));
|
||||
}
|
||||
|
||||
/// <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 GetAnimParams<T>(Identifier speciesName, AnimationType animType, string fileName = null) where T : AnimationParams, new()
|
||||
{
|
||||
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> anims))
|
||||
{
|
||||
anims = new Dictionary<string, AnimationParams>();
|
||||
allAnimations.Add(speciesName, anims);
|
||||
}
|
||||
if (fileName == null || !anims.TryGetValue(fileName, out AnimationParams anim))
|
||||
{
|
||||
string selectedFile = null;
|
||||
string folder = GetFolder(speciesName);
|
||||
if (Directory.Exists(folder))
|
||||
string folder = character.Params.VariantFile?.GetRootExcludingOverride().GetChildElement("animations")?.GetAttributeContentPath("folder", character.Prefab.ContentPackage)?.Value;
|
||||
if (folder.IsNullOrEmpty() || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var files = Directory.GetFiles(folder);
|
||||
if (files.None())
|
||||
// Use the animations defined in the base definition file.
|
||||
animSpecies = character.Prefab.GetBaseCharacterSpeciesName(speciesName);
|
||||
}
|
||||
}
|
||||
return GetAnimParams<T>(speciesName, animSpecies, fallbackSpecies: character.Prefab.GetBaseCharacterSpeciesName(speciesName), animType, file, throwErrors);
|
||||
}
|
||||
|
||||
private static readonly List<string> errorMessages = new List<string>();
|
||||
|
||||
private static T GetAnimParams<T>(Identifier speciesName, Identifier animSpecies, Identifier fallbackSpecies, AnimationType animType, Either<string, ContentPath> file, bool throwErrors = true) where T : AnimationParams, new()
|
||||
{
|
||||
Debug.Assert(!speciesName.IsEmpty);
|
||||
Debug.Assert(!animSpecies.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());
|
||||
}
|
||||
ContentPackage contentPackage = contentPath?.ContentPackage ?? CharacterPrefab.FindBySpeciesName(speciesName)?.ContentPackage;
|
||||
Debug.Assert(contentPackage != null);
|
||||
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> animations))
|
||||
{
|
||||
animations = new Dictionary<string, AnimationParams>();
|
||||
allAnimations.Add(speciesName, animations);
|
||||
}
|
||||
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(animSpecies, animType);
|
||||
if (animations.TryGetValue(key, out AnimationParams anim) && anim.AnimationType == animType)
|
||||
{
|
||||
// Already cached.
|
||||
return (T)anim;
|
||||
}
|
||||
if (!contentPath.IsNullOrEmpty())
|
||||
{
|
||||
// Load the animation from path.
|
||||
T animInstance = new T();
|
||||
if (animInstance.Load(contentPath, speciesName))
|
||||
{
|
||||
if (animInstance.AnimationType == animType)
|
||||
{
|
||||
DebugConsole.ThrowError($"[AnimationParams] Could not find any animation files from the folder: {folder}. Using the default animation.");
|
||||
selectedFile = GetDefaultFile(speciesName, animType);
|
||||
}
|
||||
var filteredFiles = FilterFilesByType(files, animType);
|
||||
if (filteredFiles.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"[AnimationParams] Could not find any animation files that match the animation type {animType} from the folder: {folder}. Using the default animation.");
|
||||
selectedFile = GetDefaultFile(speciesName, animType);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
// Files found, but none specified.
|
||||
selectedFile = GetDefaultFile(speciesName, animType);
|
||||
animations.TryAdd(contentPath.Value, animInstance);
|
||||
return animInstance;
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = filteredFiles.FirstOrDefault(f => IO.Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
|
||||
errorMessages.Add($"[AnimationParams] Animation type mismatch. Expected: {animType}, Actual: {animInstance.AnimationType}. Using the default animation.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessages.Add($"[AnimationParams] Failed to load an animation {animInstance} of type {animType} from {contentPath.Value} for the character {speciesName}. Using the default animation.");
|
||||
}
|
||||
}
|
||||
// Seek the correct animation from the character's animation folder.
|
||||
string selectedFile = null;
|
||||
string folder = GetFolder(animSpecies);
|
||||
if (Directory.Exists(folder))
|
||||
{
|
||||
string[] files = Directory.GetFiles(folder);
|
||||
if (files.None())
|
||||
{
|
||||
errorMessages.Add($"[AnimationParams] Could not find any animation files from the folder: {folder}. Using the default animation.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var filteredFiles = FilterAndSortFiles(files, animType);
|
||||
if (filteredFiles.None())
|
||||
{
|
||||
errorMessages.Add($"[AnimationParams] Could not find any animation files that match the animation type {animType} from the folder: {folder}. Using the default animation.");
|
||||
}
|
||||
else if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
// Files found, but none specified -> Get a matching animation 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(animSpecies, animType);
|
||||
selectedFile = filteredFiles.FirstOrDefault(path => PathMatchesFile(path, defaultFileName)) ?? filteredFiles.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = filteredFiles.FirstOrDefault(path => PathMatchesFile(path, fileName));
|
||||
if (selectedFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"[AnimationParams] Could not find an animation file that matches the name {fileName} and the animation type {animType}. Using the default animations.");
|
||||
selectedFile = GetDefaultFile(speciesName, animType);
|
||||
errorMessages.Add($"[AnimationParams] Could not find an animation file that matches the name {fileName} and the animation type {animType}. Using the default animations.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"[Animationparams] Invalid directory: {folder}. Using the default animation.");
|
||||
selectedFile = GetDefaultFile(speciesName, animType);
|
||||
}
|
||||
if (selectedFile == null)
|
||||
{
|
||||
throw new Exception("[AnimationParams] Selected file null!");
|
||||
}
|
||||
DebugConsole.Log($"[AnimationParams] Loading animations from {selectedFile}.");
|
||||
var characterPrefab = CharacterPrefab.Prefabs[speciesName];
|
||||
T a = new T();
|
||||
if (a.Load(ContentPath.FromRaw(characterPrefab.ContentPackage, selectedFile), speciesName))
|
||||
{
|
||||
fileName = IO.Path.GetFileNameWithoutExtension(selectedFile);
|
||||
if (!anims.ContainsKey(fileName))
|
||||
{
|
||||
anims.Add(fileName, a);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"[AnimationParams] Failed to load an animation {a} at {selectedFile} of type {animType} for the character {speciesName}",
|
||||
contentPackage: characterPrefab.ContentPackage);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return (T)anim;
|
||||
else
|
||||
{
|
||||
errorMessages.Add($"[AnimationParams] Invalid directory: {folder}. Using the default animation.");
|
||||
}
|
||||
selectedFile ??= GetDefaultFile(fallbackSpecies, animType);
|
||||
Debug.Assert(selectedFile != null);
|
||||
if (errorMessages.None())
|
||||
{
|
||||
DebugConsole.Log($"[AnimationParams] Loading animations from {selectedFile}.");
|
||||
}
|
||||
T animationInstance = new T();
|
||||
if (animationInstance.Load(ContentPath.FromRaw(contentPackage, selectedFile), speciesName))
|
||||
{
|
||||
animations.TryAdd(key, animationInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessages.Add($"[AnimationParams] Failed to load an animation {animationInstance} at {selectedFile} of type {animType} for the character {speciesName}");
|
||||
}
|
||||
foreach (string errorMsg in errorMessages)
|
||||
{
|
||||
if (throwErrors)
|
||||
{
|
||||
DebugConsole.ThrowError(errorMsg, contentPackage: contentPackage);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.Log("Logging a supressed (potential) error: " + errorMsg);
|
||||
}
|
||||
}
|
||||
errorMessages.Clear();
|
||||
return animationInstance;
|
||||
|
||||
static bool PathMatchesFile(string p, string f) => IO.Path.GetFileNameWithoutExtension(p).Equals(f, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static void ClearCache() => allAnimations.Clear();
|
||||
|
||||
public static AnimationParams Create(string fullPath, Identifier speciesName, AnimationType animationType, Type type)
|
||||
public static AnimationParams Create(string fullPath, Identifier speciesName, AnimationType animationType, Type animationParamsType)
|
||||
{
|
||||
if (type == typeof(HumanWalkParams))
|
||||
if (animationParamsType == typeof(HumanWalkParams))
|
||||
{
|
||||
return Create<HumanWalkParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(HumanRunParams))
|
||||
if (animationParamsType == typeof(HumanRunParams))
|
||||
{
|
||||
return Create<HumanRunParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(HumanSwimSlowParams))
|
||||
if (animationParamsType == typeof(HumanSwimSlowParams))
|
||||
{
|
||||
return Create<HumanSwimSlowParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(HumanSwimFastParams))
|
||||
if (animationParamsType == typeof(HumanSwimFastParams))
|
||||
{
|
||||
return Create<HumanSwimFastParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(HumanCrouchParams))
|
||||
if (animationParamsType == typeof(HumanCrouchParams))
|
||||
{
|
||||
return Create<HumanCrouchParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(FishWalkParams))
|
||||
if (animationParamsType == typeof(FishWalkParams))
|
||||
{
|
||||
return Create<FishWalkParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(FishRunParams))
|
||||
if (animationParamsType == typeof(FishRunParams))
|
||||
{
|
||||
return Create<FishRunParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(FishSwimSlowParams))
|
||||
if (animationParamsType == typeof(FishSwimSlowParams))
|
||||
{
|
||||
return Create<FishSwimSlowParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(FishSwimFastParams))
|
||||
if (animationParamsType == typeof(FishSwimFastParams))
|
||||
{
|
||||
return Create<FishSwimFastParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
throw new NotImplementedException(type.ToString());
|
||||
throw new NotImplementedException(animationParamsType.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -331,7 +374,7 @@ namespace Barotrauma
|
||||
anims = new Dictionary<string, AnimationParams>();
|
||||
allAnimations.Add(speciesName, anims);
|
||||
}
|
||||
var fileName = IO.Path.GetFileNameWithoutExtension(fullPath);
|
||||
string fileName = IO.Path.GetFileNameWithoutExtension(fullPath);
|
||||
if (anims.ContainsKey(fileName))
|
||||
{
|
||||
DebugConsole.NewMessage($"[AnimationParams] Removing the old animation of type {animationType}.", Color.Red);
|
||||
@@ -340,7 +383,8 @@ namespace Barotrauma
|
||||
var instance = new T();
|
||||
XElement animationElement = new XElement(GetDefaultFileName(speciesName, animationType), new XAttribute("animationtype", animationType.ToString()));
|
||||
instance.doc = new XDocument(animationElement);
|
||||
var characterPrefab = CharacterPrefab.Prefabs[speciesName];
|
||||
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
Debug.Assert(characterPrefab != null);
|
||||
var contentPath = ContentPath.FromRaw(characterPrefab.ContentPackage, fullPath);
|
||||
instance.UpdatePath(contentPath);
|
||||
instance.IsLoaded = instance.Deserialize(animationElement);
|
||||
@@ -373,16 +417,17 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// Update the key by removing and re-adding the animation.
|
||||
string fileName = FileNameWithoutExtension;
|
||||
if (allAnimations.TryGetValue(SpeciesName, out Dictionary<string, AnimationParams> animations))
|
||||
{
|
||||
animations.Remove(Name);
|
||||
animations.Remove(fileName);
|
||||
}
|
||||
base.UpdatePath(newPath);
|
||||
if (animations != null)
|
||||
{
|
||||
if (!animations.ContainsKey(Name))
|
||||
if (!animations.ContainsKey(fileName))
|
||||
{
|
||||
animations.Add(Name, this);
|
||||
animations.Add(fileName, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,37 +466,26 @@ namespace Barotrauma
|
||||
{
|
||||
if (isHumanoid)
|
||||
{
|
||||
switch (type)
|
||||
return type switch
|
||||
{
|
||||
case AnimationType.Walk:
|
||||
return typeof(HumanWalkParams);
|
||||
case AnimationType.Run:
|
||||
return typeof(HumanRunParams);
|
||||
case AnimationType.Crouch:
|
||||
return typeof(HumanCrouchParams);
|
||||
case AnimationType.SwimSlow:
|
||||
return typeof(HumanSwimSlowParams);
|
||||
case AnimationType.SwimFast:
|
||||
return typeof(HumanSwimFastParams);
|
||||
default:
|
||||
throw new NotImplementedException(type.ToString());
|
||||
}
|
||||
AnimationType.Walk => typeof(HumanWalkParams),
|
||||
AnimationType.Run => typeof(HumanRunParams),
|
||||
AnimationType.Crouch => typeof(HumanCrouchParams),
|
||||
AnimationType.SwimSlow => typeof(HumanSwimSlowParams),
|
||||
AnimationType.SwimFast => typeof(HumanSwimFastParams),
|
||||
_ => throw new NotImplementedException(type.ToString())
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (type)
|
||||
return type switch
|
||||
{
|
||||
case AnimationType.Walk:
|
||||
return typeof(FishWalkParams);
|
||||
case AnimationType.Run:
|
||||
return typeof(FishRunParams);
|
||||
case AnimationType.SwimSlow:
|
||||
return typeof(FishSwimSlowParams);
|
||||
case AnimationType.SwimFast:
|
||||
return typeof(FishSwimFastParams);
|
||||
default:
|
||||
throw new NotImplementedException(type.ToString());
|
||||
}
|
||||
AnimationType.Walk => typeof(FishWalkParams),
|
||||
AnimationType.Run => typeof(FishRunParams),
|
||||
AnimationType.SwimSlow => typeof(FishSwimSlowParams),
|
||||
AnimationType.SwimFast => typeof(FishSwimFastParams),
|
||||
_ => throw new NotImplementedException(type.ToString())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user