(d9829ac) v0.9.4.0

This commit is contained in:
Regalis
2019-10-24 18:05:42 +02:00
parent 9aa12bcac2
commit b39922a074
319 changed files with 12516 additions and 6815 deletions
@@ -37,7 +37,7 @@ namespace Barotrauma
}
if (!CanWalk)
{
DebugConsole.ThrowError($"{character.SpeciesName} cannot walk!");
//DebugConsole.ThrowError($"{character.SpeciesName} cannot walk!");
return null;
}
else
@@ -214,6 +214,8 @@ namespace Barotrauma
return SwimSlowParams;
case AnimationType.SwimFast:
return SwimFastParams;
case AnimationType.NotDefined:
return null;
default:
throw new NotImplementedException(type.ToString());
}
@@ -285,16 +285,9 @@ namespace Barotrauma
public override void DragCharacter(Character target, float deltaTime)
{
if (target == null) return;
Limb mouthLimb = Array.Find(Limbs, l => l != null && l.MouthPos.HasValue);
if (mouthLimb == null) mouthLimb = GetLimb(LimbType.Head);
if (mouthLimb == null)
{
DebugConsole.ThrowError("Character \"" + character.SpeciesName + "\" failed to eat a target (a head or a limb with a mouthpos required)");
return;
}
if (target == null) { return; }
Limb mouthLimb = GetLimb(LimbType.Head);
if (mouthLimb == null) { return; }
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
@@ -489,9 +482,9 @@ namespace Barotrauma
{
case LimbType.LeftFoot:
case LimbType.RightFoot:
if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID))
if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
{
SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.limbParams.ID] * Dir, MainLimb, FootTorque);
SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, MainLimb, FootTorque);
}
break;
case LimbType.Tail:
@@ -557,6 +550,9 @@ namespace Barotrauma
movementAngle -= MathHelper.TwoPi;
}
float stepLift = TargetMovement.X == 0.0f ? 0 :
(float)Math.Sin(WalkPos * CurrentGroundedParams.StepLiftFrequency + MathHelper.Pi * CurrentGroundedParams.StepLiftOffset) * (CurrentGroundedParams.StepLiftAmount / 100);
Limb torso = GetLimb(LimbType.Torso);
if (torso != null)
{
@@ -566,7 +562,7 @@ namespace Barotrauma
}
if (TorsoPosition.HasValue)
{
Vector2 pos = colliderBottom + Vector2.UnitY * TorsoPosition.Value;
Vector2 pos = colliderBottom + new Vector2(0, TorsoPosition.Value + stepLift);
if (torso != MainLimb)
{
@@ -588,7 +584,7 @@ namespace Barotrauma
}
if (HeadPosition.HasValue)
{
Vector2 pos = colliderBottom + Vector2.UnitY * HeadPosition.Value;
Vector2 pos = colliderBottom + new Vector2(0, HeadPosition.Value + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier);
if (head != MainLimb)
{
@@ -673,10 +669,10 @@ namespace Barotrauma
#if CLIENT
if (playFootstepSound) { PlayImpactSound(limb); }
#endif
if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID))
if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
{
SmoothRotateWithoutWrapping(limb,
movementAngle + CurrentGroundedParams.FootAnglesInRadians[limb.limbParams.ID] * Dir,
movementAngle + CurrentGroundedParams.FootAnglesInRadians[limb.Params.ID] * Dir,
MainLimb, FootTorque);
}
break;
@@ -637,9 +637,15 @@ namespace Barotrauma
}
else
{
if (!onGround) movement = Vector2.Zero;
if (!onGround)
{
movement = Vector2.Zero;
}
float stepLift = TargetMovement.X == 0.0f ? 0 :
(float)Math.Sin(WalkPos * CurrentGroundedParams.StepLiftFrequency + MathHelper.Pi * CurrentGroundedParams.StepLiftOffset) * (CurrentGroundedParams.StepLiftAmount / 100);
float y = colliderPos.Y;
float y = colliderPos.Y + stepLift;
if (TorsoPosition.HasValue)
{
y += TorsoPosition.Value;
@@ -648,7 +654,7 @@ namespace Barotrauma
MathUtils.SmoothStep(torso.SimPosition,
new Vector2(footMid + movement.X * TorsoLeanAmount, y), getUpForce);
y = colliderPos.Y;
y = colliderPos.Y + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier;
if (HeadPosition.HasValue)
{
y += HeadPosition.Value;
@@ -809,10 +815,11 @@ namespace Barotrauma
//get the elbow to a neutral rotation
if (Math.Abs(hand.body.AngularVelocity) < 10.0f)
{
LimbJoint elbow =
GetJointBetweenLimbs(armType, hand.type) ??
GetJointBetweenLimbs(armType, foreArmType);
hand.body.ApplyTorque(MathHelper.Clamp(-elbow.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 10.0f);
LimbJoint elbow = GetJointBetweenLimbs(armType, hand.type) ?? GetJointBetweenLimbs(armType, foreArmType);
if (elbow != null)
{
hand.body.ApplyTorque(MathHelper.Clamp(-elbow.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 10.0f);
}
}
}
}
@@ -1848,7 +1855,11 @@ namespace Barotrauma
}
var torso = GetLimb(LimbType.Torso);
var waist = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type);
Vector2 waistPos = waist.LimbA == upperLeg ? waist.WorldAnchorA : waist.WorldAnchorB;
Vector2 waistPos = Vector2.Zero;
if (waist != null)
{
waistPos = waist.LimbA == upperLeg ? waist.WorldAnchorA : waist.WorldAnchorB;
}
//distance from waist joint to the target position
float c = Vector2.Distance(pos, waistPos);
@@ -1,404 +0,0 @@
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), Editable(DecimalCount = 2, ToolTip = "How big steps the character takes.")]
public Vector2 StepSize
{
get;
set;
}
[Serialize(0f, true), Editable(DecimalCount = 2, ToolTip = "How high above the ground the character's head is positioned.")]
public float HeadPosition { get; set; }
[Serialize(0f, true), Editable(DecimalCount = 2, ToolTip = "How high above the ground the character's torso is positioned.")]
public float TorsoPosition { get; set; }
[Serialize(0.75f, true), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.99f, DecimalCount = 2, ToolTip = "The character's movement speed is multiplied with this value when moving backwards.")]
public float BackwardsMovementMultiplier { get; set; }
}
abstract class SwimParams : AnimationParams
{
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float SteerTorque { get; set; }
}
abstract class AnimationParams : EditableParams
{
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)]
public float MovementSpeed { get; set; }
[Serialize(1.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2,
ToolTip = "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)")]
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)
{
string configFilePath = Character.GetConfigFile(speciesName, contentPackage);
var folder = XMLExtensions.TryLoadXml(configFilePath)?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
{
folder = Path.Combine(Path.GetDirectoryName(configFilePath), "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).ToLowerInvariant() == fileName.ToLowerInvariant());
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))
{
if (!anims.ContainsKey(a.Name))
{
anims.Add(a.Name, 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 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(instance.Name, instance);
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
return instance as T;
}
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
protected void CreateSnapshot<T>() where T : AnimationParams, new()
{
Serialize();
if (doc == null)
{
DebugConsole.ThrowError("[AnimationParams] The source XML Document is null!");
return;
}
var copy = new T
{
IsLoaded = true,
doc = new XDocument(doc)
};
copy.Deserialize();
copy.Serialize();
memento.Store(copy);
}
public override void Undo() => Deserialize(memento.Undo().MainElement);
public override void Redo() => Deserialize(memento.Redo().MainElement);
#endregion
}
}
@@ -1,215 +0,0 @@
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 CreateSnapshot() => CreateSnapshot<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 CreateSnapshot() => CreateSnapshot<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 CreateSnapshot() => CreateSnapshot<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 CreateSnapshot() => CreateSnapshot<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;
}
[Serialize(true, true), Editable(ToolTip = "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), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the head to the correct position.")]
public float HeadMoveForce { get; set; }
[Serialize(10.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the torso to the correct position.")]
public float TorsoMoveForce { get; set; }
[Serialize(8.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the feet to the correct position.")]
public float FootMoveForce { get; set; }
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the head to the correct orientation.")]
public float HeadTorque { get; set; }
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the torso to the correct orientation.")]
public float TorsoTorque { get; set; }
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the tail to the correct orientation.")]
public float TailTorque { get; set; }
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
public float FootTorque { get; set; }
[Serialize(0.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "Optional torque that's constantly applied to legs.")]
public float LegTorque { get; set; }
/// <summary>
/// The angle of the collider when standing (i.e. out of water).
/// In degrees.
/// </summary>
[Serialize(0f, true), Editable(MinValueFloat = -360, MaxValueFloat = 360, ToolTip = "The angle of the character's collider when standing.")]
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), Editable(ToolTip = "TODO")]
public bool UseSineMovement { get; set; }
[Serialize(true, true), Editable(ToolTip = "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(true, true), Editable(ToolTip = "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(1f, true), Editable]
public float WaveAmplitude { get; set; }
[Serialize(10.0f, true), Editable]
public float WaveLength { get; set; }
[Serialize(true, true), Editable(ToolTip = "Should the character face towards the direction it's heading.")]
public bool RotateTowardsMovement { get; set; }
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the torso to the correct orientation.")]
public float TorsoTorque { get; set; }
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the head to the correct orientation.")]
public float HeadTorque { get; set; }
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the tail to the correct orientation.")]
public float TailTorque { get; set; }
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
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; }
}
}
@@ -1,169 +0,0 @@
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 CreateSnapshot() => CreateSnapshot<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 CreateSnapshot() => CreateSnapshot<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 CreateSnapshot() => CreateSnapshot<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 CreateSnapshot() => CreateSnapshot<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), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
public float FootRotateStrength { get; set; }
}
abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation
{
[Serialize(0.3f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2, ToolTip = "How much force is used to force the character upright.")]
public float GetUpForce { get; set; }
// -- TODO: use a separate clip for crawling -> replace these when implemented.
[Serialize(0.65f, true), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2, ToolTip = "Height of the torso when crouching.")]
public float CrouchingTorsoPos { get; set; }
[Serialize(0.65f, true), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2, ToolTip = "Height of the head when crouching.")]
public float CrouchingHeadPos { get; set; }
/// <summary>
/// In degrees
/// </summary>
[Serialize(-10f, true), Editable(MinValueFloat = -360, MaxValueFloat = 360, ToolTip = "Angle of the torso when crouching.")]
public float CrouchingTorsoAngle { get; set; }
/// <summary>
/// In degrees
/// </summary>
[Serialize(-10f, true), Editable(MinValueFloat = -360, MaxValueFloat = 360, ToolTip = "Angle of the head when crouching.")]
public float CrouchingHeadAngle { get; set; }
// --
[Serialize(0.25f, true), Editable(DecimalCount = 2, ToolTip = "How much the character's head leans forwards when moving.")]
public float HeadLeanAmount { get; set; }
[Serialize(0.25f, true), Editable(DecimalCount = 2, ToolTip = "How much the character's torso leans forwards when moving.")]
public float TorsoLeanAmount { get; set; }
[Serialize(15.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the feet to the correct position.")]
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), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
public float FootRotateStrength { get; set; }
[Serialize("0.0, 0.0", true), Editable(DecimalCount = 2, ToolTip = "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.")]
public Vector2 FootMoveOffset { get; set; }
[Serialize("0.0, 0.0", true), Editable(DecimalCount = 2, ToolTip = "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.")]
public Vector2 CrouchingFootMoveOffset { get; set; }
[Serialize(10.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much torque is used to bend the characters legs when taking a step.")]
public float LegBendTorque { get; set; }
[Serialize("0.4, 0.15", true), Editable(DecimalCount = 2, ToolTip = "How much the hands move along each axis.")]
public Vector2 HandMoveAmount { get; set; }
[Serialize("-0.15, 0.0", true), Editable(DecimalCount = 2, ToolTip = "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.")]
public Vector2 HandMoveOffset { get; set; }
[Serialize(0.7f, true), Editable(MinValueFloat = 0, MaxValueFloat = 2, DecimalCount = 2, ToolTip = "How much force is used to move the hands.")]
public float HandMoveStrength { get; set; }
[Serialize(-1.0f, true), Editable(DecimalCount = 2, ToolTip = "The position of the hands is clamped below this (relative to the position of the character's torso).")]
public float HandClampY { get; set; }
}
public interface IHumanAnimation
{
float FootAngle { get; set; }
float FootAngleInRadians { get; }
float FootRotateStrength { get; set; }
}
}
@@ -1,134 +0,0 @@
using System.IO;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
namespace Barotrauma
{
abstract class EditableParams : ISerializableEntity
{
public bool IsLoaded { get; protected set; }
public string Name { get; private set; }
public string FileName { get; private set; }
public string Folder { get; private set; }
public string FullPath { get; private set; }
public Dictionary<string, SerializableProperty> SerializableProperties { get; protected set; }
protected XDocument doc;
public XDocument Doc
{
get
{
if (!IsLoaded)
{
DebugConsole.ThrowError("[Params] Not loaded!");
return new XDocument();
}
return doc;
}
protected set
{
doc = value;
}
}
public XElement MainElement => doc.Root;
public XElement OriginalElement { get; protected set; }
protected virtual bool Deserialize(XElement element = null)
{
element = element ?? MainElement;
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
return SerializableProperties != null;
}
protected virtual bool Serialize(XElement element = null)
{
element = element ?? MainElement;
if (element == null)
{
DebugConsole.ThrowError("[EditableParams] The XML element is null!");
return false;
}
SerializableProperty.SerializeProperties(this, element, true);
return true;
}
protected virtual bool Load(string file)
{
UpdatePath(file);
doc = XMLExtensions.TryLoadXml(FullPath);
if (doc == null) { return false; }
IsLoaded = Deserialize(MainElement);
OriginalElement = new XElement(MainElement);
return IsLoaded;
}
protected virtual void UpdatePath(string fullPath)
{
FullPath = fullPath;
Name = Path.GetFileNameWithoutExtension(FullPath);
FileName = Path.GetFileName(FullPath);
Folder = Path.GetDirectoryName(FullPath);
}
public virtual bool Save(string fileNameWithoutExtension = null, XmlWriterSettings settings = null)
{
if (!Directory.Exists(Folder))
{
Directory.CreateDirectory(Folder);
}
OriginalElement = MainElement;
Serialize();
if (settings == null)
{
settings = new XmlWriterSettings
{
Indent = true,
OmitXmlDeclaration = true,
NewLineOnAttributes = true
};
}
if (fileNameWithoutExtension != null)
{
UpdatePath(Path.Combine(Folder, $"{fileNameWithoutExtension}.xml"));
}
using (var writer = XmlWriter.Create(FullPath, settings))
{
Doc.WriteTo(writer);
writer.Flush();
}
return true;
}
public virtual bool Reset(bool forceReload = false)
{
if (forceReload)
{
return Load(FullPath);
}
return Deserialize(OriginalElement);
}
#if CLIENT
public SerializableEntityEditor SerializableEntityEditor { get; protected set; }
public virtual void AddToEditor(ParamsEditor editor)
{
if (!IsLoaded)
{
DebugConsole.ThrowError("[Params] Not loaded!");
return;
}
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, false, true);
}
#endif
#region Memento
public readonly Memento<EditableParams> memento = new Memento<EditableParams>();
public abstract void CreateSnapshot();
public abstract void Undo();
public abstract void Redo();
public void ClearHistory() => memento.Clear();
#endregion
}
}
@@ -1,676 +0,0 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Linq;
using System.IO;
using Barotrauma.Extensions;
using System.Xml;
namespace Barotrauma
{
class HumanRagdollParams : RagdollParams
{
public static HumanRagdollParams GetRagdollParams(string speciesName, string fileName = null) => GetRagdollParams<HumanRagdollParams>(speciesName, fileName);
public static HumanRagdollParams GetDefaultRagdollParams(string speciesName) => GetDefaultRagdollParams<HumanRagdollParams>(speciesName);
}
class FishRagdollParams : RagdollParams
{
public static FishRagdollParams GetDefaultRagdollParams(string speciesName) => GetDefaultRagdollParams<FishRagdollParams>(speciesName);
}
class RagdollParams : EditableParams
{
public const float MIN_SCALE = 0.1f;
public const float MAX_SCALE = 2;
public string SpeciesName { get; private set; }
[Serialize(0f, true), Editable(-360, 360, ToolTip = "Rotation offset (in degrees) used for animations and widgets. If the sprites in the sheet are in different orientations, use the orientation of the torso for the final version of your character (while editing the character in the editor, you can change the orientation freely).")]
public float SpritesheetOrientation { get; set; }
private float limbScale;
[Serialize(1.0f, true), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)]
public float LimbScale { get { return limbScale; } set { limbScale = MathHelper.Clamp(value, MIN_SCALE, MAX_SCALE); } }
private float jointScale;
[Serialize(1.0f, true), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)]
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, false)]
public float TextureScale { get; set; }
[Serialize(45f, true), Editable(0f, 1000f)]
public float ColliderHeightFromFloor { get; set; }
[Serialize(50f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
public float ImpactTolerance { get; set; }
[Serialize(true, true), Editable]
public bool CanEnterSubmarine { get; set; }
[Serialize(true, true), Editable]
public bool Draggable { get; set; }
private static Dictionary<string, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<string, Dictionary<string, RagdollParams>>();
public List<ColliderParams> ColliderParams { get; private set; } = new List<ColliderParams>();
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
public List<JointParams> Joints { get; private set; } = new List<JointParams>();
protected IEnumerable<RagdollSubParams> GetAllSubParams() =>
ColliderParams.Select(c => c as RagdollSubParams)
.Concat(Limbs.Select(j => j as RagdollSubParams)
.Concat(Joints.Select(j => j as RagdollSubParams)));
public static string GetDefaultFileName(string speciesName) => $"{speciesName.CapitaliseFirstInvariant()}DefaultRagdoll";
public static string GetDefaultFile(string speciesName, ContentPackage contentPackage = null)
=> Path.Combine(GetFolder(speciesName, contentPackage), $"{GetDefaultFileName(speciesName)}.xml");
private static readonly object[] dummyParams = new object[]
{
new XAttribute("type", "Dummy"),
new XElement("collider", new XAttribute("radius", 1)),
new XElement("limb",
new XAttribute("id", 0),
new XAttribute("type", LimbType.Head.ToString()),
new XAttribute("width", 1),
new XAttribute("height", 1),
new XElement("sprite",
new XAttribute("sourcerect", $"0, 0, 1, 1")))
};
public static string GetFolder(string speciesName, ContentPackage contentPackage = null)
{
string configFilePath = Character.GetConfigFile(speciesName, contentPackage);
var folder = XMLExtensions.TryLoadXml(configFilePath)?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
{
folder = Path.Combine(Path.GetDirectoryName(configFilePath), "Ragdolls") + Path.DirectorySeparatorChar;
}
return folder;
}
public static T GetDefaultRagdollParams<T>(string speciesName) where T : RagdollParams, new() => GetRagdollParams<T>(speciesName, GetDefaultFileName(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>(string speciesName, string fileName = null) where T : RagdollParams, new()
{
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 selectedFile = null;
string folder = GetFolder(speciesName);
if (Directory.Exists(folder))
{
var files = Directory.GetFiles(folder);
if (files.None())
{
DebugConsole.ThrowError($"[RagdollParams] Could not find any ragdoll files from the folder: {folder}. Using the default ragdoll.");
selectedFile = GetDefaultFile(speciesName);
}
else if (string.IsNullOrEmpty(fileName))
{
// Files found, but none specified
selectedFile = GetDefaultFile(speciesName);
}
else
{
selectedFile = files.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).ToLowerInvariant() == fileName.ToLowerInvariant());
if (selectedFile == null)
{
DebugConsole.ThrowError($"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.");
selectedFile = GetDefaultFile(speciesName);
}
}
}
else
{
DebugConsole.ThrowError($"[RagdollParams] Invalid directory: {folder}. Using the default ragdoll.");
selectedFile = GetDefaultFile(speciesName);
}
if (selectedFile == null)
{
throw new Exception("[RagdollParams] Selected file null!");
}
DebugConsole.Log($"[RagdollParams] Loading ragdoll from {selectedFile}.");
T r = new T();
if (r.Load(selectedFile, speciesName))
{
if (!ragdolls.ContainsKey(r.Name))
{
ragdolls.Add(r.Name, r);
}
return r;
}
else
{
DebugConsole.ThrowError($"[RagdollParams] Failed to load ragdoll {r} at {selectedFile} for the character {speciesName}. Creating a dummy file.");
var defaultFile = GetDefaultFile(speciesName);
if (File.Exists(defaultFile))
{
DebugConsole.ThrowError($"[RagdollParams] Renaming the invalid file as {selectedFile}.invalid");
// Rename the old file so that it's not lost.
File.Move(defaultFile, defaultFile + ".invalid");
}
return CreateDefault<T>(defaultFile, speciesName, dummyParams);
}
}
return (T)ragdoll;
}
/// <summary>
/// Creates a default ragdoll for the species using a predefined configuration.
/// Note: Use only to create ragdolls for new characters, because this overrides the old ragdoll!
/// </summary>
public static T CreateDefault<T>(string fullPath, string speciesName, params object[] ragdollConfig) where T : RagdollParams, new()
{
// Remove the old ragdolls, if found.
if (allRagdolls.ContainsKey(speciesName))
{
DebugConsole.NewMessage($"[RagdollParams] Removing the old ragdolls from {speciesName}.", Color.Red);
allRagdolls.Remove(speciesName);
}
var ragdolls = new Dictionary<string, RagdollParams>();
allRagdolls.Add(speciesName, ragdolls);
var instance = new T();
XElement ragdollElement = new XElement("Ragdoll", ragdollConfig);
instance.doc = new XDocument(ragdollElement);
instance.UpdatePath(fullPath);
instance.IsLoaded = instance.Deserialize(ragdollElement);
instance.Save();
instance.Load(fullPath, speciesName);
ragdolls.Add(instance.Name, instance);
DebugConsole.NewMessage("[RagdollParams] New default ragdoll params successfully created at " + fullPath, Color.NavajoWhite);
return instance as T;
}
protected override void UpdatePath(string fullPath)
{
if (SpeciesName == null)
{
base.UpdatePath(fullPath);
}
else
{
// Update the key by removing and re-adding the ragdoll.
if (allRagdolls.TryGetValue(SpeciesName, out Dictionary<string, RagdollParams> ragdolls))
{
ragdolls.Remove(Name);
}
base.UpdatePath(fullPath);
if (ragdolls != null)
{
if (!ragdolls.ContainsKey(Name))
{
ragdolls.Add(Name, this);
}
}
}
}
public bool Save(string fileNameWithoutExtension = null)
{
OriginalElement = MainElement;
GetAllSubParams().ForEach(p => p.SetCurrentElementAsOriginalElement());
Serialize();
return base.Save(fileNameWithoutExtension, new XmlWriterSettings
{
Indent = true,
OmitXmlDeclaration = true,
NewLineOnAttributes = false
});
}
protected bool Load(string file, string speciesName)
{
if (Load(file))
{
SpeciesName = speciesName;
CreateColliders();
CreateLimbs();
CreateJoints();
return true;
}
return false;
}
public override bool Reset(bool forceReload = false)
{
if (forceReload)
{
return Load(FullPath, SpeciesName);
}
Deserialize(OriginalElement, recursive: true);
GetAllSubParams().ForEach(sp => sp.Reset());
return true;
}
protected void CreateColliders()
{
ColliderParams.Clear();
for (int i = 0; i < MainElement.Elements("collider").Count(); i++)
{
var element = MainElement.Elements("collider").ElementAt(i);
string name = i > 0 ? "Secondary Collider" : "Main Collider";
ColliderParams.Add(new ColliderParams(element, this, name));
}
}
protected void CreateLimbs()
{
Limbs.Clear();
foreach (var element in MainElement.Elements("limb"))
{
Limbs.Add(new LimbParams(element, this));
}
Limbs = Limbs.OrderBy(l => l.ID).ToList();
}
protected void CreateJoints()
{
Joints.Clear();
foreach (var element in MainElement.Elements("joint"))
{
Joints.Add(new JointParams(element, this));
}
}
protected bool Deserialize(XElement element = null, bool recursive = true)
{
if (base.Deserialize(element))
{
if (recursive)
{
GetAllSubParams().ForEach(p => p.Deserialize());
}
return true;
}
return false;
}
protected bool Serialize(XElement element = null, bool recursive = true)
{
if (base.Serialize(element))
{
if (recursive)
{
GetAllSubParams().ForEach(p => p.Serialize());
}
return true;
}
return false;
}
#region Memento
public override void CreateSnapshot()
{
Serialize();
if (doc == null)
{
DebugConsole.ThrowError("[RagdollParams] The source XML Document is null!");
return;
}
var copy = new RagdollParams
{
IsLoaded = true,
doc = new XDocument(doc)
};
copy.CreateColliders();
copy.CreateLimbs();
copy.CreateJoints();
copy.Deserialize();
copy.Serialize();
memento.Store(copy);
}
public override void Undo() => RevertTo(memento.Undo() as RagdollParams);
public override void Redo() => RevertTo(memento.Redo() as RagdollParams);
private void RevertTo(RagdollParams source)
{
if (source.MainElement == null)
{
DebugConsole.ThrowError("[RagdollParams] The source XML Element of the given RagdollParams is null!");
return;
}
Deserialize(source.MainElement, recursive: false);
var sourceSubParams = source.GetAllSubParams().ToList();
var subParams = GetAllSubParams().ToList();
// TODO: cannot currently undo joint/limb deletion.
if (sourceSubParams.Count != subParams.Count)
{
DebugConsole.ThrowError("[RagdollParams] The count of the sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes.");
return;
}
for (int i = 0; i < subParams.Count; i++)
{
var subSubParams = subParams[i].SubParams;
if (subSubParams.Count != sourceSubParams[i].SubParams.Count)
{
DebugConsole.ThrowError("[RagdollParams] The count of the sub sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes.");
return;
}
subParams[i].Deserialize(sourceSubParams[i].Element, recursive: false);
for (int j = 0; j < subSubParams.Count; j++)
{
subSubParams[j].Deserialize(sourceSubParams[i].SubParams[j].Element, recursive: false);
// Since we cannot use recursion here, we have to go deeper manually, if necessary.
}
}
}
#endregion
#if CLIENT
public void AddToEditor(ParamsEditor editor, bool alsoChildren = true)
{
base.AddToEditor(editor);
if (alsoChildren)
{
var subParams = GetAllSubParams();
foreach (var subParam in subParams)
{
subParam.AddToEditor(editor);
new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, 10), editor.EditorBox.Content.RectTransform),
style: null, color: Color.Black);
}
}
}
#endif
}
class JointParams : RagdollSubParams
{
public JointParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
private string name;
[Serialize("", true), Editable]
public override string Name
{
get
{
if (string.IsNullOrWhiteSpace(name))
{
name = GenerateName();
}
return name;
}
set
{
name = value;
}
}
public override string GenerateName() => $"Joint {Limb1} - {Limb2}";
[Serialize(-1, true), Editable]
public int Limb1 { get; set; }
[Serialize(-1, true), Editable]
public int Limb2 { get; set; }
/// <summary>
/// Should be converted to sim units.
/// </summary>
[Serialize("1.0, 1.0", true), Editable]
public Vector2 Limb1Anchor { get; set; }
/// <summary>
/// Should be converted to sim units.
/// </summary>
[Serialize("1.0, 1.0", true), Editable]
public Vector2 Limb2Anchor { get; set; }
[Serialize(true, true), Editable]
public bool CanBeSevered { get; set; }
[Serialize(true, true), Editable]
public bool LimitEnabled { get; set; }
/// <summary>
/// In degrees.
/// </summary>
[Serialize(0f, true), Editable]
public float UpperLimit { get; set; }
/// <summary>
/// In degrees.
/// </summary>
[Serialize(0f, true), Editable]
public float LowerLimit { get; set; }
[Serialize(0.25f, true), Editable]
public float Stiffness { get; set; }
}
class LimbParams : RagdollSubParams
{
public LimbParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll)
{
var spriteElement = element.Element("sprite");
if (spriteElement != null)
{
normalSpriteParams = new SpriteParams(spriteElement, ragdoll);
SubParams.Add(normalSpriteParams);
}
var damagedElement = element.Element("damagedsprite");
if (damagedElement != null)
{
damagedSpriteParams = new SpriteParams(damagedElement, ragdoll);
// Hide the damaged sprite params in the editor for now.
//SubParams.Add(damagedSpriteParams);
}
var deformElement = element.Element("deformablesprite");
if (deformElement != null)
{
deformSpriteParams = new SpriteParams(deformElement, ragdoll);
SubParams.Add(deformSpriteParams);
}
}
public readonly SpriteParams normalSpriteParams;
public readonly SpriteParams damagedSpriteParams;
public readonly SpriteParams deformSpriteParams;
private string name;
[Serialize("", true), Editable]
public override string Name
{
get
{
if (string.IsNullOrWhiteSpace(name))
{
name = GenerateName();
}
return name;
}
set
{
name = value;
}
}
public override string GenerateName() => $"Limb {ID}";
/// <summary>
/// Note that editing this in-game doesn't currently have any effect (unless the ragdoll is recreated). It should be visible, but readonly in the editor.
/// </summary>
[Serialize(-1, true), Editable]
public int ID { get; set; }
[Serialize(LimbType.None, true), Editable]
public LimbType Type { get; set; }
[Serialize(true, true), Editable]
public bool Flip { get; set; }
[Serialize(0, true), Editable]
public int HealthIndex { get; set; }
[Serialize(0f, true), Editable(ToolTip = "Higher values make AI characters prefer attacking this limb.")]
public float AttackPriority { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float SteerForce { get; set; }
[Serialize("0, 0", true), Editable(ToolTip = "Only applicable if this limb is a foot. Determines the \"neutral position\" of the foot relative to a joint determined by the \"RefJoint\" parameter. For example, a value of {-100, 0} would mean that the foot is positioned on the floor, 100 units behind the reference joint.")]
public Vector2 StepOffset { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
public float Radius { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
public float Height { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
public float Width { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10000)]
public float Mass { get; set; }
[Serialize(10f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float Density { get; set; }
[Serialize("0, 0", true), Editable(ToolTip = "The position which is used to lead the IK chain to the IK goal. Only applicable if the limb is hand or foot.")]
public Vector2 PullPos { get; set; }
[Serialize(-1, true), Editable(ToolTip = "Only applicable if this limb is a foot. Determines which joint is used as the \"neutral x-position\" for the foot movement. For example in the case of a humanoid-shaped characters this would usually be the waist. The position can be offset using the StepOffset parameter.")]
public int RefJoint { get; set; }
[Serialize(false, true), Editable]
public bool IgnoreCollisions { get; set; }
[Serialize("", true), Editable]
public string Notes { get; set; }
// Non-editable ->
[Serialize(0.3f, true)]
public float Friction { get; set; }
[Serialize(0.05f, true)]
public float Restitution { get; set; }
}
class SpriteParams : RagdollSubParams
{
public SpriteParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
[Serialize("0, 0, 0, 0", true), Editable]
public Rectangle SourceRect { get; set; }
[Serialize("0.5, 0.5", true), Editable(DecimalCount = 2, ToolTip = "Relative to the collider.")]
public Vector2 Origin { get; set; }
[Serialize(0f, true), Editable(DecimalCount = 3)]
public float Depth { get; set; }
[Serialize("", true)]
public string Texture { get; set; }
}
class ColliderParams : RagdollSubParams
{
public ColliderParams(XElement element, RagdollParams ragdoll, string name = null) : base(element, ragdoll)
{
Name = name;
}
private string name;
[Serialize("", true), Editable]
public override string Name
{
get
{
if (string.IsNullOrWhiteSpace(name))
{
name = GenerateName();
}
return name;
}
set
{
name = value;
}
}
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
public float Radius { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
public float Height { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
public float Width { get; set; }
}
abstract class RagdollSubParams : ISerializableEntity
{
public virtual string Name { get; set; }
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
public XElement Element { get; set; }
public XElement OriginalElement { get; protected set; }
public List<RagdollSubParams> SubParams { get; set; } = new List<RagdollSubParams>();
public RagdollParams Ragdoll { get; private set; }
public virtual string GenerateName() => Element.Name.ToString();
public RagdollSubParams(XElement element, RagdollParams ragdoll)
{
Element = element;
OriginalElement = new XElement(element);
Ragdoll = ragdoll;
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
}
public virtual bool Deserialize(XElement element = null, bool recursive = true)
{
element = element ?? Element;
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
if (recursive)
{
SubParams.ForEach(sp => sp.Deserialize());
}
return SerializableProperties != null;
}
public virtual bool Serialize(XElement element = null, bool recursive = true)
{
element = element ?? Element;
SerializableProperty.SerializeProperties(this, element, true);
if (recursive)
{
SubParams.ForEach(sp => sp.Serialize());
}
return true;
}
public void SetCurrentElementAsOriginalElement()
{
OriginalElement = Element;
SubParams.ForEach(sp => sp.SetCurrentElementAsOriginalElement());
}
public void Reset()
{
Deserialize(OriginalElement, false);
SubParams.ForEach(sp => sp.Reset());
}
#if CLIENT
public SerializableEntityEditor SerializableEntityEditor { get; protected set; }
public virtual void AddToEditor(ParamsEditor editor)
{
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, inGame: false, showName: true);
SubParams.ForEach(sp => sp.AddToEditor(editor));
}
#endif
}
}
@@ -10,6 +10,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using LimbParams = Barotrauma.RagdollParams.LimbParams;
using JointParams = Barotrauma.RagdollParams.JointParams;
namespace Barotrauma
{
@@ -223,7 +225,7 @@ namespace Barotrauma
{
foreach (Limb limb in Limbs)
{
if (limb.IsSevered) continue;
if (limb.IsSevered || !limb.body.PhysEnabled) { continue; }
limb.body.SetTransform(Collider.SimPosition, Collider.Rotation);
//reset pull joints (they may be somewhere far away if the character has moved from the position where animations were last updated)
limb.PullJointEnabled = false;
@@ -233,8 +235,7 @@ namespace Barotrauma
}
}
// Currently the camera cannot handle greater speeds. It starts to lag behind.
public const float MAX_SPEED = 9;
public const float MAX_SPEED = 15;
public Vector2 TargetMovement
{
@@ -258,6 +259,7 @@ namespace Barotrauma
public float ImpactTolerance => RagdollParams.ImpactTolerance;
public bool Draggable => RagdollParams.Draggable;
public bool CanEnterSubmarine => RagdollParams.CanEnterSubmarine;
public bool CanAttackSubmarine => Limbs.Any(l => l.attack != null && l.attack.IsValidTarget(AttackTarget.Structure));
public float Dir
{
@@ -317,7 +319,7 @@ namespace Barotrauma
}
else
{
items = limbs?.ToDictionary(l => l.limbParams, l => l.WearingItems);
items = limbs?.ToDictionary(l => l.Params, l => l.WearingItems);
}
foreach (var limbParams in RagdollParams.Limbs)
{
@@ -327,7 +329,7 @@ namespace Barotrauma
limbParams.Radius = 10;
}
}
foreach (var colliderParams in RagdollParams.ColliderParams)
foreach (var colliderParams in RagdollParams.Colliders)
{
if (!PhysicsBody.IsValidShape(colliderParams.Radius, colliderParams.Height, colliderParams.Width))
{
@@ -352,11 +354,16 @@ namespace Barotrauma
limb.WearingItems.AddRange(itemList);
}
}
if (character.SpeciesName.ToLowerInvariant() == "humanhusk")
if (character.IsHusk)
{
if (Limbs.None(l => l.Name.ToLowerInvariant() == "huskappendage"))
if (Character.TryGetConfigFile(character.ConfigPath, out XDocument configFile))
{
AfflictionHusk.AttachHuskAppendage(character, this);
var mainElement = configFile.Root.IsOverride() ? configFile.Root.FirstElement() : configFile.Root;
foreach (var huskAppendage in mainElement.GetChildElements("huskappendage"))
{
AfflictionHusk.AttachHuskAppendage(character, huskAppendage.GetAttributeString("affliction", string.Empty), huskAppendage, ragdoll: this);
}
}
}
}
@@ -376,7 +383,7 @@ namespace Barotrauma
}
DebugConsole.Log($"Creating colliders from {RagdollParams.Name}.");
collider = new List<PhysicsBody>();
foreach (ColliderParams cParams in RagdollParams.ColliderParams)
foreach (var cParams in RagdollParams.Colliders)
{
if (!PhysicsBody.IsValidShape(cParams.Radius, cParams.Height, cParams.Width))
{
@@ -456,14 +463,12 @@ namespace Barotrauma
/// </summary>
public void SaveRagdoll(string fileNameWithoutExtension = null)
{
SaveJoints();
SaveLimbs();
RagdollParams.Save(fileNameWithoutExtension);
}
/// <summary>
/// Resets the serializable data to the currently selected ragdoll params.
/// Force reloading always loads the xml stored in the disk.
/// Force reloading always loads the xml stored on the disk.
/// </summary>
public void ResetRagdoll(bool forceReload = false)
{
@@ -472,24 +477,6 @@ namespace Barotrauma
ResetLimbs();
}
/// <summary>
/// Saves the current joint values to the serializable joint params. This method should properly handle character flipping.
/// NOTE: Currently all the params are handled stored as SubRagdollParams and handled in the RagdollParams Save method. This method does nothing.
/// </summary>
public void SaveJoints()
{
LimbJoints.ForEach(j => j.SaveParams());
}
/// <summary>
/// Handles custom serialization per limb. Currently only the attacks need to be serialized, since they cannot be stored as SubRagdollParams (because they shouldn't be decoupled with ragdolls).
/// Note: Saving to file is not handled by this method. Calling RagdollParams.Save() after this method should work.
/// </summary>
public void SaveLimbs()
{
Limbs.ForEach(l => l.attack?.Serialize());
}
/// <summary>
/// Resets the current joint values to the serialized joint params.
/// </summary>
@@ -792,17 +779,9 @@ namespace Barotrauma
foreach (Limb limb in Limbs)
{
if (limb == null || limb.IsSevered) continue;
if (limb == null || limb.IsSevered) { continue; }
limb.Dir = Dir;
if (limb.MouthPos.HasValue)
{
limb.MouthPos = new Vector2(
-limb.MouthPos.Value.X,
limb.MouthPos.Value.Y);
}
limb.MouthPos = new Vector2(-limb.MouthPos.X, limb.MouthPos.Y);
limb.MirrorPullJoint();
}
@@ -1052,6 +1031,21 @@ namespace Barotrauma
/// </summary>
private float bodyInRestTimer;
private float BodyInRestDelay = 1.0f;
public bool BodyInRest
{
get { return bodyInRestTimer > BodyInRestDelay; }
set
{
foreach (Limb limb in Limbs)
{
limb.body.PhysEnabled = !value;
}
bodyInRestTimer = value ? BodyInRestDelay : 0.0f;
}
}
public bool forceStanding;
public void Update(float deltaTime, Camera cam)
@@ -1335,7 +1329,7 @@ namespace Barotrauma
else if (Limbs.All(l => l != null && !l.body.Enabled || l.LinearVelocity.LengthSquared() < 0.001f))
{
bodyInRestTimer += deltaTime;
if (bodyInRestTimer > 1.0f)
if (bodyInRestTimer > BodyInRestDelay)
{
foreach (Limb limb in Limbs)
{
@@ -1615,24 +1609,27 @@ namespace Barotrauma
{
if (GameMain.NetworkMember == null) return;
float lowestSubPos = ConvertUnits.ToSimUnits(Submarine.Loaded.Min(s => s.HiddenSubPosition.Y - s.Borders.Height - 128.0f));
for (int i = 0; i < character.MemState.Count; i++ )
float lowestSubPos = float.MaxValue;
if (Submarine.Loaded.Any())
{
if (character.Submarine == null)
lowestSubPos = ConvertUnits.ToSimUnits(Submarine.Loaded.Min(s => s.HiddenSubPosition.Y - s.Borders.Height - 128.0f));
for (int i = 0; i < character.MemState.Count; i++)
{
//transform in-sub coordinates to outside coordinates
if (character.MemState[i].Position.Y > lowestSubPos)
character.MemState[i].TransformInToOutside();
}
else if (currentHull?.Submarine != null)
{
//transform outside coordinates to in-sub coordinates
if (character.MemState[i].Position.Y < lowestSubPos)
character.MemState[i].TransformOutToInside(currentHull.Submarine);
if (character.Submarine == null)
{
//transform in-sub coordinates to outside coordinates
if (character.MemState[i].Position.Y > lowestSubPos)
character.MemState[i].TransformInToOutside();
}
else if (currentHull?.Submarine != null)
{
//transform outside coordinates to in-sub coordinates
if (character.MemState[i].Position.Y < lowestSubPos)
character.MemState[i].TransformOutToInside(currentHull.Submarine);
}
}
}
UpdateNetPlayerPositionProjSpecific(deltaTime, lowestSubPos);
}
@@ -1663,23 +1660,15 @@ namespace Barotrauma
public Vector2? GetMouthPosition()
{
Limb mouthLimb = Array.Find(Limbs, l => l != null && l.MouthPos.HasValue);
if (mouthLimb == null) mouthLimb = GetLimb(LimbType.Head);
if (mouthLimb == null) return null;
Vector2 mouthPos = mouthLimb.SimPosition;
if (mouthLimb.MouthPos.HasValue)
{
float cos = (float)Math.Cos(mouthLimb.Rotation);
float sin = (float)Math.Sin(mouthLimb.Rotation);
mouthPos += new Vector2(
mouthLimb.MouthPos.Value.X * cos - mouthLimb.MouthPos.Value.Y * sin,
mouthLimb.MouthPos.Value.X * sin + mouthLimb.MouthPos.Value.Y * cos) * RagdollParams.LimbScale;
}
return mouthPos;
Limb mouthLimb = GetLimb(LimbType.Head);
if (mouthLimb == null) { return null; }
float cos = (float)Math.Cos(mouthLimb.Rotation);
float sin = (float)Math.Sin(mouthLimb.Rotation);
Vector2 bodySize = mouthLimb.body.GetSize();
Vector2 offset = new Vector2(mouthLimb.MouthPos.X * bodySize.X / 2, mouthLimb.MouthPos.Y * bodySize.Y / 2);
return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos) * RagdollParams.LimbScale;
}
public Vector2 GetColliderBottom()
{
float offset = 0.0f;