(d9829ac) v0.9.4.0
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
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)
|
||||
{
|
||||
string configFilePath = Character.GetConfigFilePath(speciesName, contentPackage);
|
||||
if (!Character.TryGetConfigFile(configFilePath, out XDocument configFile))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to load config file: {configFilePath} for '{speciesName}'");
|
||||
return string.Empty;
|
||||
}
|
||||
var folder = configFile.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))
|
||||
{
|
||||
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,215 @@
|
||||
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: "TODO"), 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(1f, 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(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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
#if CLIENT
|
||||
using SoundType = Barotrauma.CharacterSound.SoundType;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains character data that should be editable in the character editor.
|
||||
/// </summary>
|
||||
class CharacterParams : EditableParams
|
||||
{
|
||||
[Serialize("", true), Editable]
|
||||
public string SpeciesName { get; private set; }
|
||||
|
||||
[Serialize("", true, description: "If defined, different species of the same group are considered like the characters of the same species by the AI."), Editable]
|
||||
public string Group { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool Humanoid { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool Husk { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool NeedsAir { get; set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool CanSpeak { get; set; }
|
||||
|
||||
[Serialize(100f, true, description: "How much noise the character makes when moving?"), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
public float Noise { get; set; }
|
||||
|
||||
[Serialize("blood", true), Editable]
|
||||
public string BloodDecal { get; private set; }
|
||||
|
||||
public readonly string File;
|
||||
|
||||
public readonly List<SubParam> SubParams = new List<SubParam>();
|
||||
public readonly List<SoundParams> Sounds = new List<SoundParams>();
|
||||
public readonly List<ParticleParams> BloodEmitters = new List<ParticleParams>();
|
||||
public readonly List<ParticleParams> GibEmitters = new List<ParticleParams>();
|
||||
public readonly List<InventoryParams> Inventories = new List<InventoryParams>();
|
||||
public HealthParams Health { get; private set; }
|
||||
public AIParams AI { get; private set; }
|
||||
|
||||
public CharacterParams(string file)
|
||||
{
|
||||
File = file;
|
||||
Load();
|
||||
}
|
||||
|
||||
protected override string GetName() => "Character Config File";
|
||||
|
||||
public override XElement MainElement => doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
|
||||
|
||||
public bool Load()
|
||||
{
|
||||
bool success = base.Load(File);
|
||||
if (string.IsNullOrEmpty(SpeciesName) && MainElement != null)
|
||||
{
|
||||
//backwards compatibility
|
||||
SpeciesName = MainElement.GetAttributeString("name", "");
|
||||
}
|
||||
CreateSubParams();
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool Save(string fileNameWithoutExtension = null)
|
||||
{
|
||||
Serialize();
|
||||
return base.Save(fileNameWithoutExtension, new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
OmitXmlDeclaration = true,
|
||||
NewLineOnAttributes = false
|
||||
});
|
||||
}
|
||||
|
||||
public override bool Reset(bool forceReload = false)
|
||||
{
|
||||
if (forceReload)
|
||||
{
|
||||
return Load();
|
||||
}
|
||||
Deserialize(OriginalElement, alsoChildren: true);
|
||||
SubParams.ForEach(sp => sp.Reset());
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CompareGroup(string group) => !string.IsNullOrWhiteSpace(group) && !string.IsNullOrWhiteSpace(Group) && group.Equals(Group, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
protected void CreateSubParams()
|
||||
{
|
||||
SubParams.Clear();
|
||||
var health = MainElement.GetChildElement("health");
|
||||
if (health != null)
|
||||
{
|
||||
Health = new HealthParams(health, this);
|
||||
SubParams.Add(Health);
|
||||
}
|
||||
// TODO: support for multiple ai elements?
|
||||
var ai = MainElement.GetChildElement("ai");
|
||||
if (ai != null)
|
||||
{
|
||||
AI = new AIParams(ai, this);
|
||||
SubParams.Add(AI);
|
||||
}
|
||||
foreach (var element in MainElement.GetChildElements("bloodemitter"))
|
||||
{
|
||||
var emitter = new ParticleParams(element, this);
|
||||
BloodEmitters.Add(emitter);
|
||||
SubParams.Add(emitter);
|
||||
}
|
||||
foreach (var element in MainElement.GetChildElements("gibemitter"))
|
||||
{
|
||||
var emitter = new ParticleParams(element, this);
|
||||
GibEmitters.Add(emitter);
|
||||
SubParams.Add(emitter);
|
||||
}
|
||||
foreach (var soundElement in MainElement.GetChildElements("sound"))
|
||||
{
|
||||
var sound = new SoundParams(soundElement, this);
|
||||
Sounds.Add(sound);
|
||||
SubParams.Add(sound);
|
||||
}
|
||||
foreach (var inventoryElement in MainElement.GetChildElements("inventory"))
|
||||
{
|
||||
var inventory = new InventoryParams(inventoryElement, this);
|
||||
Inventories.Add(inventory);
|
||||
SubParams.Add(inventory);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Deserialize(XElement element = null, bool alsoChildren = true, bool recursive = true)
|
||||
{
|
||||
if (base.Deserialize(element))
|
||||
{
|
||||
//backwards compatibility
|
||||
if (string.IsNullOrEmpty(SpeciesName))
|
||||
{
|
||||
SpeciesName = element.GetAttributeString("name", "[NAME NOT GIVEN]");
|
||||
}
|
||||
if (alsoChildren)
|
||||
{
|
||||
SubParams.ForEach(p => p.Deserialize(recursive));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Serialize(XElement element = null, bool alsoChildren = true, bool recursive = true)
|
||||
{
|
||||
if (base.Serialize(element))
|
||||
{
|
||||
if (alsoChildren)
|
||||
{
|
||||
SubParams.ForEach(p => p.Serialize(recursive));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public void AddToEditor(ParamsEditor editor, bool alsoChildren = true, bool recursive = true, int space = 0)
|
||||
{
|
||||
base.AddToEditor(editor);
|
||||
if (alsoChildren)
|
||||
{
|
||||
SubParams.ForEach(s => s.AddToEditor(editor, recursive));
|
||||
}
|
||||
if (space > 0)
|
||||
{
|
||||
new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, space), editor.EditorBox.Content.RectTransform), style: null, color: ParamsEditor.Color)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public bool AddSound() => TryAddSubParam(new XElement("sound"), (e, c) => new SoundParams(e, c), out _, Sounds);
|
||||
|
||||
public void AddInventory() => TryAddSubParam(new XElement("inventory", new XElement("item")), (e, c) => new InventoryParams(e, c), out _, Inventories);
|
||||
|
||||
public void AddBloodEmitter() => AddEmitter("bloodemitter");
|
||||
public void AddGibEmitter() => AddEmitter("gibemitter");
|
||||
|
||||
private void AddEmitter(string type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case "gibemitter":
|
||||
TryAddSubParam(new XElement(type), (e, c) => new ParticleParams(e, c), out _, GibEmitters);
|
||||
break;
|
||||
case "bloodemitter":
|
||||
TryAddSubParam(new XElement(type), (e, c) => new ParticleParams(e, c), out _, BloodEmitters);
|
||||
break;
|
||||
default: throw new NotImplementedException(type);
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveSound(SoundParams soundParams) => RemoveSubParam(soundParams);
|
||||
public bool RemoveBloodEmitter(ParticleParams emitter) => RemoveSubParam(emitter, BloodEmitters);
|
||||
public bool RemoveGibEmitter(ParticleParams emitter) => RemoveSubParam(emitter, GibEmitters);
|
||||
public bool RemoveInventory(InventoryParams inventory) => RemoveSubParam(inventory, Inventories);
|
||||
|
||||
protected bool RemoveSubParam<T>(T subParam, IList<T> collection = null) where T : SubParam
|
||||
{
|
||||
if (subParam == null || subParam.Element == null || subParam.Element.Parent == null) { return false; }
|
||||
if (collection != null && !collection.Contains(subParam)) { return false; }
|
||||
if (!SubParams.Contains(subParam)) { return false; }
|
||||
collection?.Remove(subParam);
|
||||
SubParams.Remove(subParam);
|
||||
subParam.Element.Remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool TryAddSubParam<T>(XElement element, Func<XElement, CharacterParams, T> constructor, out T subParam, IList<T> collection = null, Func<IList<T>, bool> filter = null) where T : SubParam
|
||||
{
|
||||
subParam = constructor(element, this);
|
||||
if (collection != null && filter != null)
|
||||
{
|
||||
if (filter(collection)) { return false; }
|
||||
}
|
||||
MainElement.Add(element);
|
||||
SubParams.Add(subParam);
|
||||
collection?.Add(subParam);
|
||||
return subParam != null;
|
||||
}
|
||||
|
||||
#region Subparams
|
||||
public class SoundParams : SubParam
|
||||
{
|
||||
public override string Name => "Sound";
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
public string File { get; private set; }
|
||||
|
||||
#if CLIENT
|
||||
[Serialize(SoundType.Idle, true), Editable]
|
||||
public SoundType State { get; private set; }
|
||||
#endif
|
||||
|
||||
[Serialize(1000f, true), Editable(minValue: 0f, maxValue: 10000f)]
|
||||
public float Range { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(minValue: 0f, maxValue: 2.0f)]
|
||||
public float Volume { get; private set; }
|
||||
|
||||
[Serialize(Gender.None, true, description: "Is the sound gender specific?"), Editable()]
|
||||
public Gender Gender { get; private set; }
|
||||
|
||||
public SoundParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
}
|
||||
|
||||
public class ParticleParams : SubParam
|
||||
{
|
||||
private string name;
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (name == null && Element != null)
|
||||
{
|
||||
name = Element.Name.ToString().FormatCamelCaseWithSpaces();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
public string Particle { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(-360f, 360f, decimals: 0)]
|
||||
public float AngleMin { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(-360f, 360f, decimals: 0)]
|
||||
public float AngleMax { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(0f, 100f, decimals: 2)]
|
||||
public float ScaleMin { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(0f, 100f, decimals: 2)]
|
||||
public float ScaleMax { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(0f, 10000f, decimals: 0)]
|
||||
public float VelocityMin { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(0f, 10000f, decimals: 0)]
|
||||
public float VelocityMax { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(0f, 100f, decimals: 2)]
|
||||
public float EmitInterval { get; private set; }
|
||||
|
||||
[Serialize(0, true), Editable(0, 1000)]
|
||||
public int ParticlesPerSecond { get; private set; }
|
||||
|
||||
[Serialize(0, true), Editable(0, 1000)]
|
||||
public int ParticleAmount { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool HighQualityCollisionDetection { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool CopyEntityAngle { get; private set; }
|
||||
|
||||
public ParticleParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
}
|
||||
|
||||
public class HealthParams : SubParam
|
||||
{
|
||||
public override string Name => "Health";
|
||||
|
||||
[Serialize(100f, true, description: "How much (max) health does the character have?"), Editable(minValue: 1, maxValue: 10000f)]
|
||||
public float Vitality { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool DoesBleed { get; set; }
|
||||
|
||||
[Serialize(float.NegativeInfinity, true), Editable(minValue: float.NegativeInfinity, maxValue: 0)]
|
||||
public float CrushDepth { get; set; }
|
||||
|
||||
// Make editable?
|
||||
[Serialize(false, true)]
|
||||
public bool UseHealthWindow { get; set; }
|
||||
|
||||
[Serialize(0f, true, description: "How easily the character heals from the bleeding wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float BleedingReduction { get; private set; }
|
||||
|
||||
[Serialize(0f, true, description: "How easily the character heals from the burn wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float BurnReduction { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float ConstantHealthRegeneration { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float HealthRegenerationWhenEating { get; private set; }
|
||||
|
||||
// TODO: limbhealths, sprite?
|
||||
|
||||
public HealthParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
}
|
||||
|
||||
public class InventoryParams : SubParam
|
||||
{
|
||||
public class InventoryItem : SubParam
|
||||
{
|
||||
public override string Name => "Item";
|
||||
|
||||
[Serialize("", true, description: "Item identifier."), Editable()]
|
||||
public string Identifier { get; private set; }
|
||||
|
||||
public InventoryItem(XElement element, CharacterParams character) : base(element, character) { }
|
||||
}
|
||||
|
||||
public override string Name => "Inventory";
|
||||
|
||||
[Serialize("Any, Any", true, description: "Which slots the inventory holds? Accepted types: None, Any, RightHand, LeftHand, Head, InnerClothes, OuterClothes, Headset, and Card."), Editable()]
|
||||
public string Slots { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool AccessibleWhenAlive { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "What are the odds that this inventory is spawned on the character?"), Editable(minValue: 0f, maxValue: 1.0f)]
|
||||
public float Commonness { get; private set; }
|
||||
|
||||
public List<InventoryItem> Items { get; private set; } = new List<InventoryItem>();
|
||||
|
||||
public InventoryParams(XElement element, CharacterParams character) : base(element, character)
|
||||
{
|
||||
foreach (var itemElement in element.GetChildElements("item"))
|
||||
{
|
||||
var item = new InventoryItem(itemElement, character);
|
||||
SubParams.Add(item);
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddItem(string identifier = null)
|
||||
{
|
||||
identifier = identifier ?? "";
|
||||
var element = new XElement("item", new XAttribute("identifier", identifier));
|
||||
Element.Add(element);
|
||||
var item = new InventoryItem(element, Character);
|
||||
SubParams.Add(item);
|
||||
Items.Add(item);
|
||||
}
|
||||
|
||||
public bool RemoveItem(InventoryItem item) => RemoveSubParam(item, Items);
|
||||
}
|
||||
|
||||
public class AIParams : SubParam
|
||||
{
|
||||
public override string Name => "AI";
|
||||
|
||||
[Serialize(1.0f, true, description: "How strong other characters think this character is? Only affects AI."), Editable()]
|
||||
public float CombatStrength { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "Affects how far the character can see the targets. Used as a multiplier."), Editable(minValue: 0f, maxValue: 10f)]
|
||||
public float Sight { get; private set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "Affects how far the character can hear the targets. Used as a multiplier."), Editable(minValue: 0f, maxValue: 10f)]
|
||||
public float Hearing { get; private set; }
|
||||
|
||||
[Serialize(100f, true, description: "How much the target priority increase when the character takes damage? Additive."), Editable(minValue: -1000f, maxValue: 1000f)]
|
||||
public float AggressionHurt { get; private set; }
|
||||
|
||||
[Serialize(10f, true, description: "How much the target priority increase when the character takes damage? Additive."), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
public float AggressionGreed { get; private set; }
|
||||
|
||||
[Serialize(0f, true, description: "If the health drops below this threshold, the character flees. In percentages."), Editable(minValue: 0f, maxValue: 100f)]
|
||||
public float FleeHealthThreshold { get; private set; }
|
||||
|
||||
[Serialize(false, true, description: "Does the character attack ONLY when provoked?"), Editable()]
|
||||
public bool AttackOnlyWhenProvoked { get; private set; }
|
||||
|
||||
[Serialize(true, true, description: "When true, the character retaliates quickly when it's taking damage. Enabled by default."), Editable]
|
||||
public bool RetaliateWhenTakingDamage { get; private set; }
|
||||
|
||||
[Serialize(false, true, description: "Does the character try to break inside the sub?"), Editable()]
|
||||
public bool AggressiveBoarding { get; private set; }
|
||||
|
||||
// TODO: latchonto, swarming
|
||||
|
||||
public IEnumerable<TargetParams> Targets => targets;
|
||||
protected readonly List<TargetParams> targets = new List<TargetParams>();
|
||||
|
||||
public AIParams(XElement element, CharacterParams character) : base(element, character)
|
||||
{
|
||||
element.GetChildElements("target").ForEach(t => TryAddTarget(t, out _));
|
||||
element.GetChildElements("targetpriority").ForEach(t => TryAddTarget(t, out _));
|
||||
}
|
||||
|
||||
private bool TryAddTarget(XElement targetElement, out TargetParams target)
|
||||
{
|
||||
string tag = targetElement.GetAttributeString("tag", null);
|
||||
if (!CheckTag(tag))
|
||||
{
|
||||
target = null;
|
||||
DebugConsole.ThrowError($"Multiple targets with the same tag ('{tag}') defined! Only the first will be used!");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
target = new TargetParams(targetElement, Character);
|
||||
targets.Add(target);
|
||||
SubParams.Add(target);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryAddEmptyTarget(out TargetParams targetParams) => TryAddNewTarget("newtarget" + targets.Count, AIState.Attack, 0f, out targetParams);
|
||||
|
||||
public bool TryAddNewTarget(string tag, AIState state, float priority, out TargetParams targetParams)
|
||||
{
|
||||
var element = TargetParams.CreateNewElement(tag, state, priority);
|
||||
if (TryAddTarget(element, out targetParams))
|
||||
{
|
||||
Element.Add(element);
|
||||
}
|
||||
return targetParams != null;
|
||||
}
|
||||
|
||||
private bool CheckTag(string tag)
|
||||
{
|
||||
if (tag == null) { return false; }
|
||||
tag = tag.ToLowerInvariant();
|
||||
return targets.None(t => t.Tag == tag);
|
||||
}
|
||||
|
||||
public bool RemoveTarget(TargetParams target) => RemoveSubParam(target, targets);
|
||||
|
||||
public bool TryGetTarget(string targetTag, out TargetParams target)
|
||||
{
|
||||
target = targets.FirstOrDefault(t => t.Tag == targetTag);
|
||||
return target != null;
|
||||
}
|
||||
|
||||
public TargetParams GetTarget(string targetTag, bool throwError = true)
|
||||
{
|
||||
if (!TryGetTarget(targetTag, out TargetParams target))
|
||||
{
|
||||
if (throwError)
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a target with the tag {targetTag}!");
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
public class TargetParams : SubParam
|
||||
{
|
||||
public override string Name => "Target";
|
||||
|
||||
[Serialize("", true, description: "Can be an item tag, species name or something else. Examples: decoy, provocative, light, dead, human, crawler, wall, nasonov, sonar, door, stronger, weaker, light, human, room..."), Editable()]
|
||||
public string Tag { get; private set; }
|
||||
|
||||
[Serialize(AIState.Idle, true), Editable]
|
||||
public AIState State { get; set; }
|
||||
|
||||
[Serialize(0f, true, description: "What base priority is given to the target?"), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
public float Priority { get; set; }
|
||||
|
||||
public TargetParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
|
||||
public TargetParams(string tag, AIState state, float priority, CharacterParams character) : base(CreateNewElement(tag, state, priority), character) { }
|
||||
|
||||
public static XElement CreateNewElement(string tag, AIState state, float priority)
|
||||
{
|
||||
return new XElement("target",
|
||||
new XAttribute("tag", tag),
|
||||
new XAttribute("state", state),
|
||||
new XAttribute("priority", priority));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SubParam : ISerializableEntity
|
||||
{
|
||||
public virtual string Name { get; set; }
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
public XElement Element { get; set; }
|
||||
public List<SubParam> SubParams { get; set; } = new List<SubParam>();
|
||||
|
||||
public CharacterParams Character { get; private set; }
|
||||
|
||||
public SubParam(XElement element, CharacterParams character)
|
||||
{
|
||||
Element = element;
|
||||
Character = character;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public virtual bool Deserialize(bool recursive = true)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, Element);
|
||||
if (recursive)
|
||||
{
|
||||
SubParams.ForEach(sp => sp.Deserialize(true));
|
||||
}
|
||||
return SerializableProperties != null;
|
||||
}
|
||||
|
||||
public virtual bool Serialize(bool recursive = true)
|
||||
{
|
||||
SerializableProperty.SerializeProperties(this, Element, true);
|
||||
if (recursive)
|
||||
{
|
||||
SubParams.ForEach(sp => sp.Serialize(true));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
// Don't use recursion, because the reset method might be overriden
|
||||
Deserialize(false);
|
||||
SubParams.ForEach(sp => sp.Reset());
|
||||
}
|
||||
|
||||
protected bool RemoveSubParam<T>(T subParam, IList<T> collection = null) where T : SubParam
|
||||
{
|
||||
if (subParam == null || subParam.Element == null || subParam.Element.Parent == null) { return false; }
|
||||
if (collection != null && !collection.Contains(subParam)) { return false; }
|
||||
if (!SubParams.Contains(subParam)) { return false; }
|
||||
collection?.Remove(subParam);
|
||||
SubParams.Remove(subParam);
|
||||
subParam.Element.Remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public SerializableEntityEditor SerializableEntityEditor { get; protected set; }
|
||||
public virtual void AddToEditor(ParamsEditor editor, bool recursive = true, int space = 0, ScalableFont titleFont = null)
|
||||
{
|
||||
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, inGame: false, showName: true, titleFont: titleFont ?? GUI.LargeFont);
|
||||
if (recursive)
|
||||
{
|
||||
SubParams.ForEach(sp => sp.AddToEditor(editor, true, titleFont: titleFont ?? GUI.SmallFont));
|
||||
}
|
||||
if (space > 0)
|
||||
{
|
||||
new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, space), editor.EditorBox.Content.RectTransform), style: null, color: new Color(20, 20, 20, 255))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
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 virtual XElement MainElement => doc.Root;
|
||||
public XElement OriginalElement { get; protected set; }
|
||||
|
||||
protected virtual string GetName() => Path.GetFileNameWithoutExtension(FullPath).FormatCamelCaseWithSpaces();
|
||||
|
||||
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 = GetName();
|
||||
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, int space = 0)
|
||||
{
|
||||
if (!IsLoaded)
|
||||
{
|
||||
DebugConsole.ThrowError("[Params] Not loaded!");
|
||||
return;
|
||||
}
|
||||
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, false, true, titleFont: GUI.LargeFont);
|
||||
if (space > 0)
|
||||
{
|
||||
new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, space), editor.EditorBox.Content.RectTransform), style: null, color: ParamsEditor.Color)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user