(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions
@@ -0,0 +1,435 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
public enum AnimationType
{
NotDefined,
Walk,
Run,
SwimSlow,
SwimFast
}
abstract class GroundedMovementParams : AnimationParams
{
[Serialize("1.0, 1.0", true, description: "How big steps the character takes."), Editable(DecimalCount = 2)]
public Vector2 StepSize
{
get;
set;
}
[Serialize(0f, true, description: "How high above the ground the character's head is positioned."), Editable(DecimalCount = 2)]
public float HeadPosition { get; set; }
[Serialize(0f, true, description: "How high above the ground the character's torso is positioned."), Editable(DecimalCount = 2)]
public float TorsoPosition { get; set; }
[Serialize(1f, true, description: "Separate multiplier for the head lift"), Editable(MinValueFloat = 0, MaxValueFloat = 2, ValueStep = 0.1f)]
public float StepLiftHeadMultiplier { get; set; }
[Serialize(0f, true, description: "How much the body raises when taking a step."), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 0.1f)]
public float StepLiftAmount { get; set; }
[Serialize(-0.5f, true, description: "When does the body raise when taking a step. The default (0.5) is in the middle of the step."), Editable(MinValueFloat = -1, MaxValueFloat = 1, DecimalCount = 2, ValueStep = 0.1f)]
public float StepLiftOffset { get; set; }
[Serialize(2f, true, description: "How frequently the body raises when taking a step. The default is 2 (after every step)."), Editable(MinValueFloat = 0, MaxValueFloat = 10, ValueStep = 0.1f)]
public float StepLiftFrequency { get; set; }
[Serialize(0.75f, true, description: "The character's movement speed is multiplied with this value when moving backwards."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.99f, DecimalCount = 2)]
public float BackwardsMovementMultiplier { get; set; }
}
abstract class SwimParams : AnimationParams
{
[Serialize(25.0f, true, description: "Turning speed (or rather a force applied on the main collider to make it turn). Note that you can set a limb-specific steering forces too (additional)."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float SteerTorque { get; set; }
}
abstract class AnimationParams : EditableParams, IMemorizable<AnimationParams>
{
public string SpeciesName { get; private set; }
public bool IsGroundedAnimation => AnimationType == AnimationType.Walk || AnimationType == AnimationType.Run;
public bool IsSwimAnimation => AnimationType == AnimationType.SwimSlow || AnimationType == AnimationType.SwimFast;
protected static Dictionary<string, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<string, Dictionary<string, AnimationParams>>();
[Serialize(1.0f, true), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = Ragdoll.MAX_SPEED)]
public float MovementSpeed { get; set; }
[Serialize(1.0f, true, 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)]
public float CycleSpeed { get; set; }
/// <summary>
/// In degrees.
/// </summary>
[Serialize(float.NaN, true), Editable(-360f, 360f)]
public float HeadAngle
{
get => float.IsNaN(HeadAngleInRadians) ? float.NaN : MathHelper.ToDegrees(HeadAngleInRadians);
set
{
if (!float.IsNaN(value))
{
HeadAngleInRadians = MathHelper.ToRadians(value);
}
}
}
public float HeadAngleInRadians { get; private set; } = float.NaN;
/// <summary>
/// In degrees.
/// </summary>
[Serialize(float.NaN, true), Editable(-360f, 360f)]
public float TorsoAngle
{
get => float.IsNaN(TorsoAngleInRadians) ? float.NaN : MathHelper.ToDegrees(TorsoAngleInRadians);
set
{
if (!float.IsNaN(value))
{
TorsoAngleInRadians = MathHelper.ToRadians(value);
}
}
}
public float TorsoAngleInRadians { get; private set; } = float.NaN;
[Serialize(AnimationType.NotDefined, true), Editable]
public virtual AnimationType AnimationType { get; protected set; }
public static string GetDefaultFileName(string speciesName, AnimationType animType) => $"{speciesName.CapitaliseFirstInvariant()}{animType.ToString()}";
public static string GetDefaultFile(string speciesName, AnimationType animType, ContentPackage contentPackage = null)
=> Path.Combine(GetFolder(speciesName, contentPackage), $"{GetDefaultFileName(speciesName, animType)}.xml");
public static string GetFolder(string speciesName, ContentPackage contentPackage = null)
{
CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(speciesName);
if (prefab?.XDocument == null)
{
DebugConsole.ThrowError($"Failed to find config file for '{speciesName}'");
return string.Empty;
}
return GetFolder(prefab.XDocument, prefab.FilePath);
}
public static string GetFolder(XDocument doc, string filePath)
{
var folder = doc.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
{
folder = Path.Combine(Path.GetDirectoryName(filePath), "Animations");
}
return folder;
}
/// <summary>
/// Selects a random filepath from multiple paths, matching the specified animation type.
/// </summary>
public static string GetRandomFilePath(IEnumerable<string> filePaths, AnimationType type)
{
return filePaths.GetRandom(f => AnimationPredicate(f, type), Rand.RandSync.Server);
}
/// <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))
{
typeString = doc.Root.GetAttributeString("AnimationType", "NotDefined");
}
return Enum.TryParse(typeString, out AnimationType fileType) && fileType == type;
}
public static T GetDefaultAnimParams<T>(string speciesName, AnimationType animType) where T : AnimationParams, new() => 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>(string 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))
{
var files = Directory.GetFiles(folder);
if (files.None())
{
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);
}
else
{
selectedFile = filteredFiles.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
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);
}
}
}
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}.");
T a = new T();
if (a.Load(selectedFile, speciesName))
{
fileName = 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}");
}
return a;
}
return (T)anim;
}
public static void ClearCache() => allAnimations.Clear();
public static AnimationParams Create(string fullPath, string speciesName, AnimationType animationType, Type type)
{
if (type == typeof(HumanWalkParams))
{
return Create<HumanWalkParams>(fullPath, speciesName, animationType);
}
if (type == typeof(HumanRunParams))
{
return Create<HumanRunParams>(fullPath, speciesName, animationType);
}
if (type == typeof(HumanSwimSlowParams))
{
return Create<HumanSwimSlowParams>(fullPath, speciesName, animationType);
}
if (type == typeof(HumanSwimFastParams))
{
return Create<HumanSwimFastParams>(fullPath, speciesName, animationType);
}
if (type == typeof(FishWalkParams))
{
return Create<FishWalkParams>(fullPath, speciesName, animationType);
}
if (type == typeof(FishRunParams))
{
return Create<FishRunParams>(fullPath, speciesName, animationType);
}
if (type == typeof(FishSwimSlowParams))
{
return Create<FishSwimSlowParams>(fullPath, speciesName, animationType);
}
if (type == typeof(FishSwimFastParams))
{
return Create<FishSwimFastParams>(fullPath, speciesName, animationType);
}
throw new NotImplementedException(type.ToString());
}
/// <summary>
/// Note: Overrides old animations, if found!
/// </summary>
public static T Create<T>(string fullPath, string speciesName, AnimationType animationType) where T : AnimationParams, new()
{
if (animationType == AnimationType.NotDefined)
{
throw new Exception("Cannot create an animation file of type " + animationType.ToString());
}
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> anims))
{
anims = new Dictionary<string, AnimationParams>();
allAnimations.Add(speciesName, anims);
}
var fileName = Path.GetFileNameWithoutExtension(fullPath);
if (anims.ContainsKey(fileName))
{
DebugConsole.NewMessage($"[AnimationParams] Removing the old animation of type {animationType}.", Color.Red);
anims.Remove(fileName);
}
var instance = new T();
XElement animationElement = new XElement(GetDefaultFileName(speciesName, animationType), new XAttribute("animationtype", animationType.ToString()));
instance.doc = new XDocument(animationElement);
instance.UpdatePath(fullPath);
instance.IsLoaded = instance.Deserialize(animationElement);
instance.Save();
instance.Load(fullPath, speciesName);
anims.Add(fileName, instance);
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
return instance as T;
}
public bool Serialize() => base.Serialize();
public bool Deserialize() => base.Deserialize();
protected bool Load(string file, string speciesName)
{
if (Load(file))
{
SpeciesName = speciesName;
return true;
}
return false;
}
protected override void UpdatePath(string newPath)
{
if (SpeciesName == null)
{
base.UpdatePath(newPath);
}
else
{
// Update the key by removing and re-adding the animation.
if (allAnimations.TryGetValue(SpeciesName, out Dictionary<string, AnimationParams> animations))
{
animations.Remove(Name);
}
base.UpdatePath(newPath);
if (animations != null)
{
if (!animations.ContainsKey(Name))
{
animations.Add(Name, this);
}
}
}
}
protected static string ParseFootAngles(Dictionary<int, float> footAngles)
{
//convert to the format "id1:angle,id2:angle,id3:angle"
return string.Join(",", footAngles.Select(kv => kv.Key + ": " + kv.Value.ToString("G", CultureInfo.InvariantCulture)).ToArray());
}
protected static void SetFootAngles(Dictionary<int, float> footAngles, string value)
{
footAngles.Clear();
if (string.IsNullOrEmpty(value))
{
return;
}
string[] keyValuePairs = value.Split(',');
foreach (string joinedKvp in keyValuePairs)
{
string[] keyValuePair = joinedKvp.Split(':');
if (keyValuePair.Length != 2 ||
!int.TryParse(keyValuePair[0].Trim(), out int limbIndex) ||
!float.TryParse(keyValuePair[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float angle))
{
DebugConsole.ThrowError("Failed to parse foot angles (" + value + ")");
continue;
}
footAngles[limbIndex] = angle;
}
}
public static Type GetParamTypeFromAnimType(AnimationType type, bool isHumanoid)
{
if (isHumanoid)
{
switch (type)
{
case AnimationType.Walk:
return typeof(HumanWalkParams);
case AnimationType.Run:
return typeof(HumanRunParams);
case AnimationType.SwimSlow:
return typeof(HumanSwimSlowParams);
case AnimationType.SwimFast:
return typeof(HumanSwimFastParams);
default:
throw new NotImplementedException(type.ToString());
}
}
else
{
switch (type)
{
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());
}
}
}
#region Memento
public Memento<AnimationParams> Memento { get; protected set; } = new Memento<AnimationParams>();
public abstract void StoreSnapshot();
protected void StoreSnapshot<T>() where T : AnimationParams, new()
{
if (doc == null)
{
DebugConsole.ThrowError("[AnimationParams] The source XML Document is null!");
return;
}
Serialize();
var copy = new T
{
IsLoaded = true,
doc = new XDocument(doc)
};
copy.Deserialize();
copy.Serialize();
Memento.Store(copy);
}
public void Undo() => Deserialize(Memento.Undo().MainElement);
public void Redo() => Deserialize(Memento.Redo().MainElement);
public void ClearHistory() => Memento.Clear();
#endregion
}
}
@@ -0,0 +1,218 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace Barotrauma
{
class FishWalkParams : FishGroundedParams
{
public static FishWalkParams GetDefaultAnimParams(Character character)
{
return Check(character) ? GetDefaultAnimParams<FishWalkParams>(character.SpeciesName, AnimationType.Walk) : Empty;
}
public static FishWalkParams GetAnimParams(Character character, string fileName = null)
{
return Check(character) ? GetAnimParams<FishWalkParams>(character.SpeciesName, AnimationType.Walk, fileName) : Empty;
}
protected static FishWalkParams Empty = new FishWalkParams();
public override void StoreSnapshot() => StoreSnapshot<FishWalkParams>();
}
class FishRunParams : FishGroundedParams
{
public static FishRunParams GetDefaultAnimParams(Character character)
{
return Check(character) ? GetDefaultAnimParams<FishRunParams>(character.SpeciesName, AnimationType.Run) : Empty;
}
public static FishRunParams GetAnimParams(Character character, string fileName = null)
{
return Check(character) ? GetAnimParams<FishRunParams>(character.SpeciesName, AnimationType.Run, fileName) : Empty;
}
protected static FishRunParams Empty = new FishRunParams();
public override void StoreSnapshot() => StoreSnapshot<FishRunParams>();
}
class FishSwimFastParams : FishSwimParams
{
public static FishSwimFastParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<FishSwimFastParams>(character.SpeciesName, AnimationType.SwimFast);
public static FishSwimFastParams GetAnimParams(Character character, string fileName = null)
{
return GetAnimParams<FishSwimFastParams>(character.SpeciesName, AnimationType.SwimFast, fileName);
}
public override void StoreSnapshot() => StoreSnapshot<FishSwimFastParams>();
}
class FishSwimSlowParams : FishSwimParams
{
public static FishSwimSlowParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<FishSwimSlowParams>(character.SpeciesName, AnimationType.SwimSlow);
public static FishSwimSlowParams GetAnimParams(Character character, string fileName = null)
{
return GetAnimParams<FishSwimSlowParams>(character.SpeciesName, AnimationType.SwimSlow, fileName);
}
public override void StoreSnapshot() => StoreSnapshot<FishSwimSlowParams>();
}
abstract class FishGroundedParams : GroundedMovementParams, IFishAnimation
{
protected static bool Check(Character character)
{
if (!character.AnimController.CanWalk)
{
DebugConsole.ThrowError($"{character.SpeciesName} cannot use run animations!");
return false;
}
return true;
}
[Editable, Serialize(true, true, description: "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
public bool Flip { get; set; }
[Serialize(10.0f, true, description: "How much force is used to move the head to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float HeadMoveForce { get; set; }
[Serialize(10.0f, true, description: "How much force is used to move the torso to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float TorsoMoveForce { get; set; }
[Serialize(8.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootMoveForce { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float HeadTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float TorsoTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float TailTorque { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float FootTorque { get; set; }
[Serialize(0.0f, true, description: "Optional torque that's constantly applied to legs."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float LegTorque { get; set; }
/// <summary>
/// The angle of the collider when standing (i.e. out of water).
/// In degrees.
/// </summary>
[Serialize(0f, true, description: "The angle of the character's collider when standing."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
public float ColliderStandAngle
{
get => MathHelper.ToDegrees(ColliderStandAngleInRadians);
set => ColliderStandAngleInRadians = MathHelper.ToRadians(value);
}
public float ColliderStandAngleInRadians { get; private set; }
[Serialize(null, true), Editable]
public string FootAngles
{
get => ParseFootAngles(FootAnglesInRadians);
set => SetFootAngles(FootAnglesInRadians, value);
}
/// <summary>
/// Key = limb id, value = angle in radians
/// </summary>
public Dictionary<int, float> FootAnglesInRadians { get; set; } = new Dictionary<int, float>();
/// <summary>
/// In degrees.
/// </summary>
[Serialize(float.NaN, true), Editable(-360f, 360f)]
public float TailAngle
{
get => float.IsNaN(TailAngleInRadians) ? float.NaN : MathHelper.ToDegrees(TailAngleInRadians);
set
{
if (!float.IsNaN(value))
{
TailAngleInRadians = MathHelper.ToRadians(value);
}
}
}
public float TailAngleInRadians { get; private set; } = float.NaN;
}
abstract class FishSwimParams : SwimParams, IFishAnimation
{
[Serialize(false, true, description: "Instead of linear movement (default), use a wave-like movement. Note: WaveAmplitude and WaveLength don't have any effect on this. It's synced with the movement speed."), Editable]
public bool UseSineMovement { get; set; }
[Editable, Serialize(true, true, description: "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
public bool Flip { get; set; }
[Editable, Serialize(true, true, description: "If enabled, the character will simply be mirrored horizontally when it wants to turn around. If disabled, it will rotate itself to face the other direction.")]
public bool Mirror { get; set; }
[Serialize(5f, true), Editable]
public float WaveAmplitude { get; set; }
[Serialize(10.0f, true), Editable]
public float WaveLength { get; set; }
[Editable, Serialize(true, true, description: "Should the character face towards the direction it's heading.")]
public bool RotateTowardsMovement { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float TorsoTorque { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float HeadTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float TailTorque { get; set; }
[Serialize(1f, true, description: "Multiplier applied based on the angle difference between the tail and the main limb. Increasing the value prevents snake-like characters from getting tangled on themselves. Default = 1 (no boost)"), Editable(MinValueFloat = 1, MaxValueFloat = 100)]
public float TailTorqueMultiplier { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float FootTorque { get; set; }
[Serialize(null, true), Editable]
public string FootAngles
{
get => ParseFootAngles(FootAnglesInRadians);
set => SetFootAngles(FootAnglesInRadians, value);
}
/// <summary>
/// Key = limb id, value = angle in radians
/// </summary>
public Dictionary<int, float> FootAnglesInRadians { get; set; } = new Dictionary<int, float>();
/// <summary>
/// In degrees.
/// </summary>
[Serialize(float.NaN, true), Editable(-360f, 360f)]
public float TailAngle
{
get => float.IsNaN(TailAngleInRadians) ? float.NaN : MathHelper.ToDegrees(TailAngleInRadians);
set
{
if (!float.IsNaN(value))
{
TailAngleInRadians = MathHelper.ToRadians(value);
}
}
}
public float TailAngleInRadians { get; private set; } = float.NaN;
}
interface IFishAnimation
{
bool Flip { get; set; }
string FootAngles { get; set; }
Dictionary<int, float> FootAnglesInRadians { get; set; }
float TailAngle { get; set; }
float TailAngleInRadians { get; }
float HeadTorque { get; set; }
float TorsoTorque { get; set; }
float TailTorque { get; set; }
float FootTorque { get; set; }
}
}
@@ -0,0 +1,169 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class HumanWalkParams : HumanGroundedParams
{
public static HumanWalkParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanWalkParams>(character.SpeciesName, AnimationType.Walk);
public static HumanWalkParams GetAnimParams(Character character, string fileName = null)
{
return GetAnimParams<HumanWalkParams>(character.SpeciesName, AnimationType.Walk, fileName);
}
public override void StoreSnapshot() => StoreSnapshot<HumanWalkParams>();
}
class HumanRunParams : HumanGroundedParams
{
public static HumanRunParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanRunParams>(character.SpeciesName, AnimationType.Run);
public static HumanRunParams GetAnimParams(Character character, string fileName = null)
{
return GetAnimParams<HumanRunParams>(character.SpeciesName, AnimationType.Run, fileName);
}
public override void StoreSnapshot() => StoreSnapshot<HumanRunParams>();
}
class HumanSwimFastParams: HumanSwimParams
{
public static HumanSwimFastParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanSwimFastParams>(character.SpeciesName, AnimationType.SwimFast);
public static HumanSwimFastParams GetAnimParams(Character character, string fileName = null)
{
return GetAnimParams<HumanSwimFastParams>(character.SpeciesName, AnimationType.SwimFast, fileName);
}
public override void StoreSnapshot() => StoreSnapshot<HumanSwimFastParams>();
}
class HumanSwimSlowParams : HumanSwimParams
{
public static HumanSwimSlowParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanSwimSlowParams>(character.SpeciesName, AnimationType.SwimSlow);
public static HumanSwimSlowParams GetAnimParams(Character character, string fileName = null)
{
return GetAnimParams<HumanSwimSlowParams>(character.SpeciesName, AnimationType.SwimSlow, fileName);
}
public override void StoreSnapshot() => StoreSnapshot<HumanSwimSlowParams>();
}
abstract class HumanSwimParams : SwimParams, IHumanAnimation
{
[Serialize(0.5f, true), Editable(DecimalCount = 2)]
public float LegMoveAmount { get; set; }
[Serialize(5.0f, true), Editable]
public float LegCycleLength { get; set; }
[Serialize("0.5, 0.1", true), Editable(DecimalCount = 2)]
public Vector2 HandMoveAmount { get; set; }
[Serialize(0.5f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HandMoveStrength { get; set; }
[Serialize(5.0f, true), Editable]
public float HandCycleSpeed { get; set; }
[Serialize("0.0, 0.0", true), Editable(DecimalCount = 2)]
public Vector2 HandMoveOffset { get; set; }
/// <summary>
/// In degrees.
/// </summary>
[Serialize(0.0f, true), Editable(-360f, 360f)]
public float FootAngle
{
get => MathHelper.ToDegrees(FootAngleInRadians);
set
{
FootAngleInRadians = MathHelper.ToRadians(value);
}
}
public float FootAngleInRadians { get; private set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootRotateStrength { get; set; }
}
abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation
{
[Serialize(0.3f, true, description: "How much force is used to force the character upright."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2)]
public float GetUpForce { get; set; }
// -- TODO: use a separate clip for crawling -> replace these when implemented.
[Serialize(0.65f, true, description: "Height of the torso when crouching."), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2)]
public float CrouchingTorsoPos { get; set; }
[Serialize(0.65f, true, description: "Height of the head when crouching."), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2)]
public float CrouchingHeadPos { get; set; }
/// <summary>
/// In degrees
/// </summary>
[Serialize(-10f, true, description: "Angle of the torso when crouching."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
public float CrouchingTorsoAngle { get; set; }
/// <summary>
/// In degrees
/// </summary>
[Serialize(-10f, true, description: "Angle of the head when crouching."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
public float CrouchingHeadAngle { get; set; }
// --
[Serialize(0.25f, true, description: "How much the character's head leans forwards when moving."), Editable(DecimalCount = 2)]
public float HeadLeanAmount { get; set; }
[Serialize(0.25f, true, description: "How much the character's torso leans forwards when moving."), Editable(DecimalCount = 2)]
public float TorsoLeanAmount { get; set; }
[Serialize(15.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootMoveStrength { get; set; }
/// <summary>
/// In degrees.
/// </summary>
[Serialize(0.0f, true), Editable(-360f, 360f)]
public float FootAngle
{
get => MathHelper.ToDegrees(FootAngleInRadians);
set
{
FootAngleInRadians = MathHelper.ToRadians(value);
}
}
public float FootAngleInRadians { get; private set; }
[Serialize(20.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootRotateStrength { get; set; }
[Serialize("0.0, 0.0", true, description: "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them."), Editable(DecimalCount = 2)]
public Vector2 FootMoveOffset { get; set; }
[Serialize("0.0, 0.0", true, description: "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them."), Editable(DecimalCount = 2)]
public Vector2 CrouchingFootMoveOffset { get; set; }
[Serialize(10.0f, true, description: "How much torque is used to bend the characters legs when taking a step."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float LegBendTorque { get; set; }
[Serialize("0.4, 0.15", true, description: "How much the hands move along each axis."), Editable(DecimalCount = 2)]
public Vector2 HandMoveAmount { get; set; }
[Serialize("-0.15, 0.0", true, description: "Added to the calculated hand positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their hands one unit behind them."), Editable(DecimalCount = 2)]
public Vector2 HandMoveOffset { get; set; }
[Serialize(0.7f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 2, DecimalCount = 2)]
public float HandMoveStrength { get; set; }
[Serialize(-1.0f, true, description: "The position of the hands is clamped below this (relative to the position of the character's torso)."), Editable(DecimalCount = 2)]
public float HandClampY { get; set; }
}
public interface IHumanAnimation
{
float FootAngle { get; set; }
float FootAngleInRadians { get; }
float FootRotateStrength { get; set; }
}
}