Client LuaCsForBarotrauma
AnimationParams.cs
1 using Microsoft.Xna.Framework;
2 using System.Collections.Generic;
3 using System.Globalization;
4 using Barotrauma.IO;
5 using System;
6 using System.Diagnostics;
7 using System.Linq;
8 using System.Xml.Linq;
10 
11 namespace Barotrauma
12 {
13  public enum AnimationType
14  {
15  NotDefined = 0,
16  Walk = 1,
17  Run = 2,
18  SwimSlow = 3,
19  SwimFast = 4,
20  Crouch = 5
21  }
22 
24  {
25  [Header("Legs")]
26  [Serialize("1.0, 1.0", IsPropertySaveable.Yes, description: "How big steps the character takes."), Editable(DecimalCount = 2, ValueStep = 0.01f)]
27  public Vector2 StepSize
28  {
29  get;
30  set;
31  }
32 
33  [Header("Standing")]
34  [Serialize(0f, IsPropertySaveable.Yes, description: "How high above the ground the character's head is positioned."), Editable(DecimalCount = 2, ValueStep = 0.1f)]
35  public float HeadPosition { get; set; }
36 
37  [Serialize(0f, IsPropertySaveable.Yes, description: "How high above the ground the character's torso is positioned."), Editable(DecimalCount = 2, ValueStep = 0.1f)]
38  public float TorsoPosition { get; set; }
39 
40  [Header("Step lift")]
41  [Serialize(1f, IsPropertySaveable.Yes, description: "Separate multiplier for the head lift"), Editable(MinValueFloat = 0, MaxValueFloat = 2, ValueStep = 0.1f)]
42  public float StepLiftHeadMultiplier { get; set; }
43 
44  [Serialize(0f, IsPropertySaveable.Yes, description: "How much the body raises when taking a step."), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 0.1f)]
45  public float StepLiftAmount { get; set; }
46 
47  [Serialize(0.5f, IsPropertySaveable.Yes, 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)]
48  public float StepLiftOffset { get; set; }
49 
50  [Serialize(2f, IsPropertySaveable.Yes, 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)]
51  public float StepLiftFrequency { get; set; }
52 
53  [Header("Movement")]
54  [Serialize(0.75f, IsPropertySaveable.Yes, description: "The character's movement speed is multiplied with this value when moving backwards."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.99f, DecimalCount = 2)]
55  public float BackwardsMovementMultiplier { get; set; }
56 
57  [Serialize(1.0f, IsPropertySaveable.Yes, description: "Adjusts the maximum speed while climbing. The actual speed is affected by the MovementSpeed."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 10f, DecimalCount = 2)]
58  public float ClimbSpeed { get; set; }
59 
60  [Serialize(2.0f, IsPropertySaveable.Yes, description: "Used instead of ClimbSpeed when descending ladders while moving fast (running). Not used if lower than ClimbSpeed."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 10f, DecimalCount = 2)]
61  public float SlideSpeed { get; set; }
62 
63  [Serialize(10.5f, IsPropertySaveable.Yes, description: "Force applied to the main collider, torso and head, when climbing ladders."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 100f, DecimalCount = 1)]
64  public float ClimbBodyMoveForce { get; set; }
65 
66  [Serialize(5.2f, IsPropertySaveable.Yes, description: "Force applied to the hands when climbing ladders."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 100f, DecimalCount = 1)]
67  public float ClimbHandMoveForce { get; set; }
68 
69  [Serialize(10.0f, IsPropertySaveable.Yes, description: "Force applied to the feet when climbing ladders."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 100f, DecimalCount = 1)]
70  public float ClimbFootMoveForce { get; set; }
71 
72  [Serialize(30.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.1f, MaxValueFloat = 100f, DecimalCount = 1)]
73  public float ClimbStepHeight { get; set; }
74 
75  protected override bool Deserialize(XElement element = null)
76  {
77  if (element.GetAttributeEnum(nameof(AnimationType), AnimationType.NotDefined) is AnimationType.Run)
78  {
79  // These values were previously hard-coded when running, so we need to set different default values for the run animations, when they are not defined.
80  const string climbSpeedName = nameof(ClimbSpeed);
81  if (element.GetAttribute(climbSpeedName) == null)
82  {
83  element.SetAttribute(climbSpeedName, 2.0f);
84  }
85  const string climbStepName = nameof(ClimbStepHeight);
86  if (element.GetAttribute(climbStepName) == null)
87  {
88  element.SetAttribute(climbStepName, 60.0f);
89  }
90  const string slideSpeedName = nameof(SlideSpeed);
91  if (element.GetAttribute(slideSpeedName) == null)
92  {
93  element.SetAttribute(slideSpeedName, 4.0f);
94  }
95  }
96  return base.Deserialize(element);
97  }
98  }
99 
100  abstract class SwimParams : AnimationParams
101  {
102  [Serialize(25.0f, IsPropertySaveable.Yes, 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 = 1000, ValueStep = 1)]
103  public float SteerTorque { get; set; }
104 
105  [Serialize(25.0f, IsPropertySaveable.Yes, description: "How much torque is used to move the legs."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
106  public float LegTorque { get; set; }
107  }
108 
109  abstract class AnimationParams : EditableParams, IMemorizable<AnimationParams>
110  {
111  public Identifier SpeciesName { get; private set; }
113  public bool IsSwimAnimation => AnimationType is AnimationType.SwimSlow or AnimationType.SwimFast;
114 
115  [Header("General")]
117  public virtual AnimationType AnimationType { get; protected set; }
121  private static readonly Dictionary<Identifier, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<Identifier, Dictionary<string, AnimationParams>>();
122 
123  [Header("Movement")]
124  [Serialize(1.0f, IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = Ragdoll.MAX_SPEED, ValueStep = 0.1f)]
125  public float MovementSpeed { get; set; }
126 
127  [Serialize(1.0f, IsPropertySaveable.Yes, description: "The speed of the \"animation cycle\", i.e. how fast the character takes steps or moves the tail/legs/arms (the outcome depends what the clip is about)"),
128  Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2, ValueStep = 0.01f)]
129  public float CycleSpeed { get; set; }
130 
134  [Header("Orientation")]
135  [Serialize(float.NaN, IsPropertySaveable.Yes), Editable(-360f, 360f)]
136  public float HeadAngle
137  {
138  get => float.IsNaN(HeadAngleInRadians) ? float.NaN : MathHelper.ToDegrees(HeadAngleInRadians);
139  set
140  {
141  if (!float.IsNaN(value))
142  {
143  HeadAngleInRadians = MathHelper.ToRadians(value);
144  }
145  }
146  }
147  public float HeadAngleInRadians { get; private set; } = float.NaN;
148 
152  [Serialize(float.NaN, IsPropertySaveable.Yes), Editable(-360f, 360f)]
153  public float TorsoAngle
154  {
155  get => float.IsNaN(TorsoAngleInRadians) ? float.NaN : MathHelper.ToDegrees(TorsoAngleInRadians);
156  set
157  {
158  if (!float.IsNaN(value))
159  {
160  TorsoAngleInRadians = MathHelper.ToRadians(value);
161  }
162  }
163  }
164 
165  public float TorsoAngleInRadians { get; private set; } = float.NaN;
166 
167  [Serialize(50.0f, IsPropertySaveable.Yes, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
168  public float HeadTorque { get; set; }
169 
170  [Serialize(50.0f, IsPropertySaveable.Yes, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
171  public float TorsoTorque { get; set; }
172 
173  [Header("Legs")]
174  [Serialize(25.0f, IsPropertySaveable.Yes, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
175  public float FootTorque { get; set; }
176 
177  [Header("Arms")]
178  [Serialize(1f, IsPropertySaveable.Yes, description: "How much force is used to rotate the arms to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
179  public float ArmIKStrength { get; set; }
180 
181  [Serialize(1f, IsPropertySaveable.Yes, description: "How much force is used to rotate the hands to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
182  public float HandIKStrength { get; set; }
183 
184  public static string GetDefaultFileName(Identifier speciesName, AnimationType animType) => $"{speciesName.Value.CapitaliseFirstInvariant()}{animType}";
185  public static string GetDefaultFile(Identifier speciesName, AnimationType animType) => Barotrauma.IO.Path.Combine(GetFolder(speciesName), $"{GetDefaultFileName(speciesName, animType)}.xml");
186 
187  public static string GetFolder(Identifier speciesName)
188  {
189  CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(speciesName);
190  if (prefab?.ConfigElement == null)
191  {
192  DebugConsole.ThrowError($"Failed to find config file for '{speciesName}'");
193  return string.Empty;
194  }
195  return GetFolder(prefab.ConfigElement, prefab.FilePath.Value);
196  }
197 
198  private static string GetFolder(ContentXElement root, string filePath)
199  {
200  Debug.Assert(filePath != null);
201  Debug.Assert(root != null);
202  string folder = root.GetChildElement("animations")?.GetAttributeContentPath("folder")?.Value;
203  if (string.IsNullOrEmpty(folder) || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
204  {
205  folder = IO.Path.Combine(IO.Path.GetDirectoryName(filePath), "Animations");
206  }
207  return folder.CleanUpPathCrossPlatform(correctFilenameCase: true);
208  }
209 
213  public static IEnumerable<string> FilterAndSortFiles(IEnumerable<string> filePaths, AnimationType type)
214  {
215  return filePaths.Where(f => AnimationPredicate(f, type)).OrderBy(f => f, StringComparer.OrdinalIgnoreCase);
216 
217  static bool AnimationPredicate(string filePath, AnimationType type)
218  {
219  XDocument doc = XMLExtensions.TryLoadXml(filePath);
220  if (doc == null) { return false; }
221  return doc.GetRootExcludingOverride().GetAttributeEnum("animationtype", AnimationType.NotDefined) == type;
222  }
223  }
224 
225  protected static T GetDefaultAnimParams<T>(Character character, AnimationType animType) where T : AnimationParams, new()
226  {
227  // Using a null file definition means we are taking a first matching file from the folder.
228  return GetAnimParams<T>(character, animType, file: null, throwErrors: true);
229  }
230 
231  protected static T GetAnimParams<T>(Character character, AnimationType animType, Either<string, ContentPath> file, bool throwErrors = true) where T : AnimationParams, new()
232  {
233  Identifier speciesName = character.SpeciesName;
234  Identifier animSpecies = speciesName;
235  if (!character.VariantOf.IsEmpty)
236  {
237  string folder = character.Params.VariantFile?.GetRootExcludingOverride().GetChildElement("animations")?.GetAttributeContentPath("folder", character.Prefab.ContentPackage)?.Value;
238  if (folder.IsNullOrEmpty() || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
239  {
240  // Use the animations defined in the base definition file.
241  animSpecies = character.Prefab.GetBaseCharacterSpeciesName(speciesName);
242  }
243  }
244  return GetAnimParams<T>(speciesName, animSpecies, fallbackSpecies: character.Prefab.GetBaseCharacterSpeciesName(speciesName), animType, file, throwErrors);
245  }
246 
247  private static readonly List<string> errorMessages = new List<string>();
248 
249  private static T GetAnimParams<T>(Identifier speciesName, Identifier animSpecies, Identifier fallbackSpecies, AnimationType animType, Either<string, ContentPath> file, bool throwErrors = true) where T : AnimationParams, new()
250  {
251  Debug.Assert(!speciesName.IsEmpty);
252  Debug.Assert(!animSpecies.IsEmpty);
253  ContentPath contentPath = null;
254  string fileName = null;
255  if (file != null)
256  {
257  if (!file.TryGet(out fileName))
258  {
259  file.TryGet(out contentPath);
260  }
261  Debug.Assert(!fileName.IsNullOrWhiteSpace() || !contentPath.IsNullOrWhiteSpace());
262  }
263  ContentPackage contentPackage = contentPath?.ContentPackage ?? CharacterPrefab.FindBySpeciesName(speciesName)?.ContentPackage;
264  Debug.Assert(contentPackage != null);
265  if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> animations))
266  {
267  animations = new Dictionary<string, AnimationParams>();
268  allAnimations.Add(speciesName, animations);
269  }
270  string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(animSpecies, animType);
271  if (animations.TryGetValue(key, out AnimationParams anim) && anim.AnimationType == animType)
272  {
273  // Already cached.
274  return (T)anim;
275  }
276  if (!contentPath.IsNullOrEmpty())
277  {
278  // Load the animation from path.
279  T animInstance = new T();
280  if (animInstance.Load(contentPath, speciesName))
281  {
282  if (animInstance.AnimationType == animType)
283  {
284  animations.TryAdd(contentPath.Value, animInstance);
285  return animInstance;
286  }
287  else
288  {
289  errorMessages.Add($"[AnimationParams] Animation type mismatch. Expected: {animType}, Actual: {animInstance.AnimationType}. Using the default animation.");
290  }
291  }
292  else
293  {
294  errorMessages.Add($"[AnimationParams] Failed to load an animation {animInstance} of type {animType} from {contentPath.Value} for the character {speciesName}. Using the default animation.");
295  }
296  }
297  // Seek the correct animation from the character's animation folder.
298  string selectedFile = null;
299  string folder = GetFolder(animSpecies);
300  if (Directory.Exists(folder))
301  {
302  string[] files = Directory.GetFiles(folder);
303  if (files.None())
304  {
305  errorMessages.Add($"[AnimationParams] Could not find any animation files from the folder: {folder}. Using the default animation.");
306  }
307  else
308  {
309  var filteredFiles = FilterAndSortFiles(files, animType);
310  if (filteredFiles.None())
311  {
312  errorMessages.Add($"[AnimationParams] Could not find any animation files that match the animation type {animType} from the folder: {folder}. Using the default animation.");
313  }
314  else if (string.IsNullOrEmpty(fileName))
315  {
316  // Files found, but none specified -> Get a matching animation from the specified folder.
317  // First try to find a file that matches the default file name. If that fails, just take any file.
318  string defaultFileName = GetDefaultFileName(animSpecies, animType);
319  selectedFile = filteredFiles.FirstOrDefault(path => PathMatchesFile(path, defaultFileName)) ?? filteredFiles.First();
320  }
321  else
322  {
323  selectedFile = filteredFiles.FirstOrDefault(path => PathMatchesFile(path, fileName));
324  if (selectedFile == null)
325  {
326  errorMessages.Add($"[AnimationParams] Could not find an animation file that matches the name {fileName} and the animation type {animType}. Using the default animations.");
327  }
328  }
329  }
330  }
331  else
332  {
333  errorMessages.Add($"[AnimationParams] Invalid directory: {folder}. Using the default animation.");
334  }
335  selectedFile ??= GetDefaultFile(fallbackSpecies, animType);
336  Debug.Assert(selectedFile != null);
337  if (errorMessages.None())
338  {
339  DebugConsole.Log($"[AnimationParams] Loading animations from {selectedFile}.");
340  }
341  T animationInstance = new T();
342  if (animationInstance.Load(ContentPath.FromRaw(contentPackage, selectedFile), speciesName))
343  {
344  animations.TryAdd(key, animationInstance);
345  }
346  else
347  {
348  errorMessages.Add($"[AnimationParams] Failed to load an animation {animationInstance} at {selectedFile} of type {animType} for the character {speciesName}");
349  }
350  foreach (string errorMsg in errorMessages)
351  {
352  if (throwErrors)
353  {
354  DebugConsole.ThrowError(errorMsg, contentPackage: contentPackage);
355  }
356  else
357  {
358  DebugConsole.Log("Logging a supressed (potential) error: " + errorMsg);
359  }
360  }
361  errorMessages.Clear();
362  return animationInstance;
363 
364  static bool PathMatchesFile(string p, string f) => IO.Path.GetFileNameWithoutExtension(p).Equals(f, StringComparison.OrdinalIgnoreCase);
365  }
366 
367  public static void ClearCache() => allAnimations.Clear();
368 
369  public static AnimationParams Create(string fullPath, Identifier speciesName, AnimationType animationType, Type animationParamsType)
370  {
371  if (animationParamsType == typeof(HumanWalkParams))
372  {
373  return Create<HumanWalkParams>(fullPath, speciesName, animationType);
374  }
375  if (animationParamsType == typeof(HumanRunParams))
376  {
377  return Create<HumanRunParams>(fullPath, speciesName, animationType);
378  }
379  if (animationParamsType == typeof(HumanSwimSlowParams))
380  {
381  return Create<HumanSwimSlowParams>(fullPath, speciesName, animationType);
382  }
383  if (animationParamsType == typeof(HumanSwimFastParams))
384  {
385  return Create<HumanSwimFastParams>(fullPath, speciesName, animationType);
386  }
387  if (animationParamsType == typeof(HumanCrouchParams))
388  {
389  return Create<HumanCrouchParams>(fullPath, speciesName, animationType);
390  }
391  if (animationParamsType == typeof(FishWalkParams))
392  {
393  return Create<FishWalkParams>(fullPath, speciesName, animationType);
394  }
395  if (animationParamsType == typeof(FishRunParams))
396  {
397  return Create<FishRunParams>(fullPath, speciesName, animationType);
398  }
399  if (animationParamsType == typeof(FishSwimSlowParams))
400  {
401  return Create<FishSwimSlowParams>(fullPath, speciesName, animationType);
402  }
403  if (animationParamsType == typeof(FishSwimFastParams))
404  {
405  return Create<FishSwimFastParams>(fullPath, speciesName, animationType);
406  }
407  throw new NotImplementedException(animationParamsType.ToString());
408  }
409 
413  public static T Create<T>(string fullPath, Identifier speciesName, AnimationType animationType) where T : AnimationParams, new()
414  {
415  if (animationType == AnimationType.NotDefined)
416  {
417  throw new Exception("Cannot create an animation file of type " + animationType.ToString());
418  }
419  if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> anims))
420  {
421  anims = new Dictionary<string, AnimationParams>();
422  allAnimations.Add(speciesName, anims);
423  }
424  string fileName = IO.Path.GetFileNameWithoutExtension(fullPath);
425  if (anims.ContainsKey(fileName))
426  {
427  DebugConsole.NewMessage($"[AnimationParams] Removing the old animation of type {animationType}.", Color.Red);
428  anims.Remove(fileName);
429  }
430  var instance = new T();
431  XElement animationElement = new XElement(GetDefaultFileName(speciesName, animationType), new XAttribute("animationtype", animationType.ToString()));
432  instance.doc = new XDocument(animationElement);
433  var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
434  Debug.Assert(characterPrefab != null);
435  var contentPath = ContentPath.FromRaw(characterPrefab.ContentPackage, fullPath);
436  instance.UpdatePath(contentPath);
437  instance.IsLoaded = instance.Deserialize(animationElement);
438  instance.Save();
439  instance.Load(contentPath, speciesName);
440  anims.Add(fileName, instance);
441  DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
442  return instance;
443  }
444 
445  public bool Serialize() => base.Serialize();
446  public bool Deserialize() => base.Deserialize();
447 
448  protected bool Load(ContentPath file, Identifier speciesName)
449  {
450  if (Load(file))
451  {
452  SpeciesName = speciesName;
453  return true;
454  }
455  return false;
456  }
457 
458  protected override void UpdatePath(ContentPath newPath)
459  {
460  if (SpeciesName == null)
461  {
462  base.UpdatePath(newPath);
463  }
464  else
465  {
466  // Update the key by removing and re-adding the animation.
467  string fileName = FileNameWithoutExtension;
468  if (allAnimations.TryGetValue(SpeciesName, out Dictionary<string, AnimationParams> animations))
469  {
470  animations.Remove(fileName);
471  }
472  base.UpdatePath(newPath);
473  if (animations != null)
474  {
475  if (!animations.ContainsKey(fileName))
476  {
477  animations.Add(fileName, this);
478  }
479  }
480  }
481  }
482 
483  protected static string ParseFootAngles(Dictionary<int, float> footAngles)
484  {
485  //convert to the format "id1:angle,id2:angle,id3:angle"
486  return string.Join(",", footAngles.Select(kv => kv.Key + ": " + kv.Value.ToString("G", CultureInfo.InvariantCulture)).ToArray());
487  }
488 
489  protected static void SetFootAngles(Dictionary<int, float> footAngles, string value)
490  {
491  footAngles.Clear();
492  if (string.IsNullOrEmpty(value))
493  {
494  return;
495  }
496 
497  string[] keyValuePairs = value.Split(',');
498  foreach (string joinedKvp in keyValuePairs)
499  {
500  string[] keyValuePair = joinedKvp.Split(':');
501  if (keyValuePair.Length != 2 ||
502  !int.TryParse(keyValuePair[0].Trim(), out int limbIndex) ||
503  !float.TryParse(keyValuePair[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float angle))
504  {
505  DebugConsole.ThrowError("Failed to parse foot angles (" + value + ")");
506  continue;
507  }
508  footAngles[limbIndex] = angle;
509  }
510  }
511 
512  public static Type GetParamTypeFromAnimType(AnimationType type, bool isHumanoid)
513  {
514  if (isHumanoid)
515  {
516  return type switch
517  {
518  AnimationType.Walk => typeof(HumanWalkParams),
519  AnimationType.Run => typeof(HumanRunParams),
520  AnimationType.Crouch => typeof(HumanCrouchParams),
521  AnimationType.SwimSlow => typeof(HumanSwimSlowParams),
522  AnimationType.SwimFast => typeof(HumanSwimFastParams),
523  _ => throw new NotImplementedException(type.ToString())
524  };
525  }
526  else
527  {
528  return type switch
529  {
530  AnimationType.Walk => typeof(FishWalkParams),
531  AnimationType.Run => typeof(FishRunParams),
532  AnimationType.SwimSlow => typeof(FishSwimSlowParams),
533  AnimationType.SwimFast => typeof(FishSwimFastParams),
534  _ => throw new NotImplementedException(type.ToString())
535  };
536  }
537  }
538 
539  #region Memento
540  public Memento<AnimationParams> Memento { get; protected set; } = new Memento<AnimationParams>();
541  public abstract void StoreSnapshot();
542  protected void StoreSnapshot<T>() where T : AnimationParams, new()
543  {
544  if (doc == null)
545  {
546  DebugConsole.ThrowError("[AnimationParams] The source XML Document is null!");
547  return;
548  }
549  Serialize();
550  var copy = new T
551  {
552  IsLoaded = true,
553  doc = new XDocument(doc),
554  Path = Path
555  };
556  copy.Deserialize();
557  copy.Serialize();
558  Memento.Store(copy);
559  }
560  public void Undo() => Deserialize(Memento.Undo().MainElement);
561  public void Redo() => Deserialize(Memento.Redo().MainElement);
562  public void ClearHistory() => Memento.Clear();
563  #endregion
564  }
565 }
static string ParseFootAngles(Dictionary< int, float > footAngles)
static Type GetParamTypeFromAnimType(AnimationType type, bool isHumanoid)
static IEnumerable< string > FilterAndSortFiles(IEnumerable< string > filePaths, AnimationType type)
Selects all file paths that match the specified animation type and filters them alphabetically.
static T GetAnimParams< T >(Character character, AnimationType animType, Either< string, ContentPath > file, bool throwErrors=true)
static AnimationParams Create(string fullPath, Identifier speciesName, AnimationType animationType, Type animationParamsType)
static string GetDefaultFile(Identifier speciesName, AnimationType animType)
abstract void StoreSnapshot()
static T GetDefaultAnimParams< T >(Character character, AnimationType animType)
float? HeadAngle
In degrees.
static string GetDefaultFileName(Identifier speciesName, AnimationType animType)
static T Create< T >(string fullPath, Identifier speciesName, AnimationType animationType)
Note: Overrides old animations, if found!
float? TorsoAngle
In degrees.
bool Load(ContentPath file, Identifier speciesName)
static void SetFootAngles(Dictionary< int, float > footAngles, string value)
override void UpdatePath(ContentPath newPath)
static string GetFolder(Identifier speciesName)
Identifier GetBaseCharacterSpeciesName(Identifier speciesName)
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
ContentXElement ConfigElement
ContentPackage(XDocument doc, string path)
readonly? ContentPackage ContentPackage
Definition: ContentPath.cs:21
static ContentPath FromRaw(string? rawValue)
string???????????? Value
Definition: ContentPath.cs:27
ContentPath? GetAttributeContentPath(string key)
ContentXElement? GetChildElement(string name)
override bool Deserialize(XElement element=null)
void Store(T newState)
Definition: Memento.cs:25
ContentPackage? ContentPackage
Definition: Prefab.cs:37
ContentPath FilePath
Definition: Prefab.cs:38