Client LuaCsForBarotrauma
RagdollParams.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.Xml.Linq;
6 using System.Linq;
7 using Barotrauma.IO;
8 using System.Xml;
10 using FarseerPhysics;
11 #if CLIENT
13 #endif
14 
15 namespace Barotrauma
16 {
17  public enum CanEnterSubmarine
18  {
22  False,
26  True,
31  Partial
32  }
33 
35  {
36  public static HumanRagdollParams GetDefaultRagdollParams(Character character) => GetDefaultRagdollParams<HumanRagdollParams>(character);
37  }
38 
40  {
41  public static FishRagdollParams GetDefaultRagdollParams(Character character) => GetDefaultRagdollParams<FishRagdollParams>(character);
42  }
43 
44  class RagdollParams : EditableParams, IMemorizable<RagdollParams>
45  {
46  #region Ragdoll
47  public const float MIN_SCALE = 0.1f;
48  public const float MAX_SCALE = 2;
49 
50  public Identifier SpeciesName { get; private set; }
51 
52  [Serialize("", IsPropertySaveable.Yes, description: "Default path for the limb sprite textures. Used only if the limb specific path for the limb is not defined"), Editable]
53  public string Texture { get; set; }
54 
55  [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable()]
56  public Color Color { get; set; }
57 
58  [Serialize(0.0f, IsPropertySaveable.Yes, description: "General orientation of the sprites as drawn on the spritesheet. " +
59  "Defines the \"forward direction\" of the sprites. Should be configured as the direction pointing outwards from the main limb. " +
60  "Incorrectly defined orientations may lead to limbs being rotated incorrectly when e.g. when the character aims or flips to face a different direction. " +
61  "Can be overridden per sprite by setting a value for Limb's 'Sprite Orientation'."), Editable(-360, 360)]
62  public float SpritesheetOrientation { get; set; }
63 
65  {
66  get
67  {
68  return
69  (SpritesheetOrientation > 45.0f && SpritesheetOrientation < 135.0f) ||
70  (SpritesheetOrientation > 255.0f && SpritesheetOrientation < 315.0f);
71  }
72  }
73 
74  private float limbScale;
75  [Serialize(1.0f, IsPropertySaveable.Yes), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)]
76  public float LimbScale
77  {
78  get { return limbScale; }
79  set { limbScale = MathHelper.Clamp(value, MIN_SCALE, MAX_SCALE); }
80  }
81 
82  private float jointScale;
83  [Serialize(1.0f, IsPropertySaveable.Yes), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)]
84  public float JointScale
85  {
86  get { return jointScale; }
87  set { jointScale = MathHelper.Clamp(value, MIN_SCALE, MAX_SCALE); }
88  }
89 
96  public float TextureScale { get; set; }
97 
102  [Serialize(1f, IsPropertySaveable.No)]
103  public float SourceRectScale { get; set; }
104 
105  [Serialize(45f, IsPropertySaveable.Yes, description: "How high from the ground the main collider levitates when the character is standing? Doesn't affect swimming."), Editable(0f, 1000f)]
106  public float ColliderHeightFromFloor { get; set; }
107 
108  [Serialize(50f, IsPropertySaveable.Yes, description: "How much impact is required before the character takes impact damage?"), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
109  public float ImpactTolerance { get; set; }
110 
111  [Serialize(CanEnterSubmarine.True, IsPropertySaveable.Yes, description: "Can the creature enter submarine. Creatures that cannot enter submarines, always collide with it, even when there is a gap."), Editable()]
113 
115  public bool CanWalk { get; set; }
116 
117  [Serialize(true, IsPropertySaveable.Yes, description: "Can the character be dragged around by other creatures?"), Editable()]
118  public bool Draggable { get; set; }
119 
121  public LimbType MainLimb { get; set; }
122 
128  private static readonly Dictionary<Identifier, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<Identifier, Dictionary<string, RagdollParams>>();
129 
130  public List<ColliderParams> Colliders { get; private set; } = new List<ColliderParams>();
131  public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
132  public List<JointParams> Joints { get; private set; } = new List<JointParams>();
133 
134  protected IEnumerable<SubParam> GetAllSubParams() =>
135  Colliders
136  .Concat<SubParam>(Limbs)
137  .Concat(Joints);
138 
139  public static string GetDefaultFileName(Identifier speciesName) => $"{speciesName.Value.CapitaliseFirstInvariant()}DefaultRagdoll";
140  public static string GetDefaultFile(Identifier speciesName, ContentPackage contentPackage = null)
141  => IO.Path.Combine(GetFolder(speciesName, contentPackage), $"{GetDefaultFileName(speciesName)}.xml");
142 
143  public static string GetFolder(Identifier speciesName, ContentPackage contentPackage = null)
144  {
145  CharacterPrefab prefab = CharacterPrefab.Find(p => p.Identifier == speciesName && (contentPackage == null || p.ContentFile.ContentPackage == contentPackage));
146  if (prefab?.ConfigElement == null)
147  {
148  DebugConsole.ThrowError($"Failed to find config file for '{speciesName}'", contentPackage: contentPackage);
149  return string.Empty;
150  }
151  return GetFolder(prefab.ConfigElement, prefab.ContentFile.Path.Value);
152  }
153 
154  private static string GetFolder(ContentXElement root, string filePath)
155  {
156  Debug.Assert(filePath != null);
157  Debug.Assert(root != null);
158  string folder = (root.GetChildElement("ragdolls") ?? root.GetChildElement("ragdoll"))?.GetAttributeContentPath("folder")?.Value;
159  if (folder.IsNullOrEmpty() || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
160  {
161  folder = IO.Path.Combine(IO.Path.GetDirectoryName(filePath), "Ragdolls") + IO.Path.DirectorySeparatorChar;
162  }
163  return folder.CleanUpPathCrossPlatform(correctFilenameCase: true);
164  }
165 
166  public static T GetDefaultRagdollParams<T>(Character character) where T : RagdollParams, new() => GetDefaultRagdollParams<T>(character.SpeciesName, character.Params, character.Prefab.ContentPackage);
167 
168  public static T GetDefaultRagdollParams<T>(Identifier speciesName, CharacterParams characterParams, ContentPackage contentPackage) where T : RagdollParams, new()
169  {
170  XElement mainElement = characterParams.VariantFile?.Root ?? characterParams.MainElement;
171  return GetDefaultRagdollParams<T>(speciesName, mainElement, contentPackage);
172  }
173 
174  public static T GetDefaultRagdollParams<T>(Identifier speciesName, XElement characterRootElement, ContentPackage contentPackage) where T : RagdollParams, new()
175  {
176  Debug.Assert(contentPackage != null);
177  if (characterRootElement.IsOverride())
178  {
179  characterRootElement = characterRootElement.FirstElement();
180  }
181  Identifier ragdollSpecies = speciesName;
182  Identifier variantOf = characterRootElement.VariantOf();
183  if (characterRootElement != null && (characterRootElement.GetChildElement("ragdolls") ?? characterRootElement.GetChildElement("ragdoll")) is XElement ragdollElement)
184  {
185  if ((ragdollElement.GetAttributeContentPath("path", contentPackage) ?? ragdollElement.GetAttributeContentPath("file", contentPackage)) is ContentPath path)
186  {
187  return GetRagdollParams<T>(speciesName, ragdollSpecies, file: path, contentPackage);
188  }
189  else if (!variantOf.IsEmpty)
190  {
191  string folder = ragdollElement.GetAttributeContentPath("folder", contentPackage)?.Value;
192  if (folder.IsNullOrEmpty() || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
193  {
194  // Folder attribute not defined or set to default -> use the ragdoll defined in the base definition file.
195  if (CharacterPrefab.FindBySpeciesName(variantOf) is CharacterPrefab prefab)
196  {
197  ragdollSpecies = prefab.GetBaseCharacterSpeciesName(variantOf);
198  }
199  }
200  }
201  }
202  else if (!variantOf.IsEmpty && CharacterPrefab.FindBySpeciesName(variantOf) is CharacterPrefab prefab)
203  {
204  // Ragdoll element not defined -> use the ragdoll defined in the base definition file.
205  ragdollSpecies = prefab.GetBaseCharacterSpeciesName(variantOf);
206  }
207  // Using a null file definition means we use the default animations found in the Ragdolls folder.
208  return GetRagdollParams<T>(speciesName, ragdollSpecies, file: null, contentPackage);
209  }
210 
211  public static T GetRagdollParams<T>(Identifier speciesName, Identifier ragdollSpecies, Either<string, ContentPath> file, ContentPackage contentPackage) where T : RagdollParams, new()
212  {
213  Debug.Assert(!speciesName.IsEmpty);
214  Debug.Assert(!ragdollSpecies.IsEmpty);
215  ContentPath contentPath = null;
216  string fileName = null;
217  if (file != null)
218  {
219  if (!file.TryGet(out fileName))
220  {
221  file.TryGet(out contentPath);
222  }
223  Debug.Assert(!fileName.IsNullOrWhiteSpace() || !contentPath.IsNullOrWhiteSpace());
224  }
225  Debug.Assert(contentPackage != null);
226  if (!allRagdolls.TryGetValue(speciesName, out Dictionary<string, RagdollParams> ragdolls))
227  {
228  ragdolls = new Dictionary<string, RagdollParams>();
229  allRagdolls.Add(speciesName, ragdolls);
230  }
231  string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(ragdollSpecies);
232  if (ragdolls.TryGetValue(key, out RagdollParams ragdoll))
233  {
234  // Already cached.
235  return (T)ragdoll;
236  }
237  if (!contentPath.IsNullOrEmpty())
238  {
239  // Load the ragdoll from path.
240  T ragdollInstance = new T();
241  if (ragdollInstance.Load(contentPath, ragdollSpecies))
242  {
243  ragdolls.TryAdd(contentPath.Value, ragdollInstance);
244  return ragdollInstance;
245  }
246  else
247  {
248  DebugConsole.ThrowError($"[AnimationParams] Failed to load an animation {ragdollInstance} from {contentPath.Value} for the character {speciesName}. Using the default ragdoll.", contentPackage: contentPackage);
249  }
250  }
251  // Seek the default ragdoll from the character's ragdoll folder.
252  string selectedFile;
253  string folder = GetFolder(ragdollSpecies);
254  if (Directory.Exists(folder))
255  {
256  var files = Directory.GetFiles(folder).OrderBy(f => f, StringComparer.OrdinalIgnoreCase);
257  if (files.None())
258  {
259  DebugConsole.ThrowError($"[RagdollParams] Could not find any ragdoll files from the folder: {folder}. Using the default ragdoll.", contentPackage: contentPackage);
260  selectedFile = GetDefaultFile(ragdollSpecies);
261  }
262  else
263  {
264  if (string.IsNullOrEmpty(fileName))
265  {
266  // Files found, but none specified -> Get a matching ragdoll from the specified folder.
267  // First try to find a file that matches the default file name. If that fails, just take any file.
268  string defaultFileName = GetDefaultFileName(ragdollSpecies);
269  selectedFile = files.FirstOrDefault(f => f.Contains(defaultFileName, StringComparison.OrdinalIgnoreCase)) ?? files.First();
270  }
271  else
272  {
273  selectedFile = files.FirstOrDefault(f => IO.Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
274  if (selectedFile == null)
275  {
276  DebugConsole.ThrowError($"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.", contentPackage: contentPackage);
277  selectedFile = GetDefaultFile(ragdollSpecies);
278  }
279  }
280  }
281  }
282  else
283  {
284  DebugConsole.ThrowError($"[RagdollParams] Invalid directory: {folder}. Using the default ragdoll.", contentPackage: contentPackage);
285  selectedFile = GetDefaultFile(ragdollSpecies);
286  }
287 
288  Debug.Assert(selectedFile != null);
289  DebugConsole.Log($"[RagdollParams] Loading the ragdoll from {selectedFile}.");
290  T r = new T();
291  if (r.Load(ContentPath.FromRaw(contentPackage, selectedFile), speciesName))
292  {
293  ragdolls.TryAdd(key, r);
294  }
295  else
296  {
297  // Failing to create a ragdoll causes so many issues that cannot be handled. Dummy ragdoll just seems to make things harder to debug. It's better to fail early.
298  throw new Exception($"[RagdollParams] Failed to load ragdoll {r.Name} from {selectedFile} for the character {speciesName}.");
299  }
300  return r;
301  }
302 
307  public static T CreateDefault<T>(string fullPath, Identifier speciesName, XElement mainElement) where T : RagdollParams, new()
308  {
309  // Remove the old ragdolls, if found.
310  if (allRagdolls.ContainsKey(speciesName))
311  {
312  DebugConsole.NewMessage($"[RagdollParams] Removing the old ragdolls from {speciesName}.", Color.Red);
313  allRagdolls.Remove(speciesName);
314  }
315  var ragdolls = new Dictionary<string, RagdollParams>();
316  allRagdolls.Add(speciesName, ragdolls);
317  var instance = new T
318  {
319  doc = new XDocument(mainElement)
320  };
321  var characterPrefab = CharacterPrefab.Prefabs[speciesName];
322  var contentPath = ContentPath.FromRaw(characterPrefab.ContentPackage, fullPath);
323  instance.UpdatePath(contentPath);
324  instance.IsLoaded = instance.Deserialize(mainElement);
325  instance.Save();
326  instance.Load(contentPath, speciesName);
327  ragdolls.Add(instance.FileNameWithoutExtension, instance);
328  DebugConsole.NewMessage("[RagdollParams] New default ragdoll params successfully created at " + fullPath, Color.NavajoWhite);
329  return instance;
330  }
331 
332  public static void ClearCache() => allRagdolls.Clear();
333 
334  protected override void UpdatePath(ContentPath fullPath)
335  {
336  if (SpeciesName == null)
337  {
338  base.UpdatePath(fullPath);
339  }
340  else
341  {
342  // Update the key by removing and re-adding the ragdoll.
343  string fileName = FileNameWithoutExtension;
344  if (allRagdolls.TryGetValue(SpeciesName, out Dictionary<string, RagdollParams> ragdolls))
345  {
346  ragdolls.Remove(fileName);
347  }
348  base.UpdatePath(fullPath);
349  if (ragdolls != null)
350  {
351  if (!ragdolls.ContainsKey(fileName))
352  {
353  ragdolls.Add(fileName, this);
354  }
355  }
356  }
357  }
358 
359  public bool Save(string fileNameWithoutExtension = null)
360  {
362  GetAllSubParams().ForEach(p => p.SetCurrentElementAsOriginalElement());
363  Serialize();
364  return base.Save(fileNameWithoutExtension, new XmlWriterSettings
365  {
366  Indent = true,
367  OmitXmlDeclaration = true,
368  NewLineOnAttributes = false
369  });
370  }
371 
372  protected bool Load(ContentPath file, Identifier speciesName)
373  {
374  if (Load(file))
375  {
376  isVariantScaleApplied = false;
377  SpeciesName = speciesName;
378  CreateColliders();
379  CreateLimbs();
380  CreateJoints();
381  return true;
382  }
383  return false;
384  }
385 
389  public void Apply()
390  {
391  Serialize();
392  }
393 
397  public override bool Reset(bool forceReload = false)
398  {
399  if (forceReload)
400  {
401  return Load(Path, SpeciesName);
402  }
403  // Don't use recursion, because the reset method might be overriden
404  Deserialize(OriginalElement, alsoChildren: false, recursive: false);
405  GetAllSubParams().ForEach(sp => sp.Reset());
406  return true;
407  }
408 
409  protected void CreateColliders()
410  {
411  Colliders.Clear();
412  if (MainElement?.GetChildElements("collider") is { } colliderElements)
413  {
414  for (int i = 0; i < colliderElements.Count(); i++)
415  {
416  var element = colliderElements.ElementAt(i);
417  string name = i > 0 ? "Secondary Collider" : "Main Collider";
418  Colliders.Add(new ColliderParams(element, this, name));
419  }
420  }
421  }
422 
423  protected void CreateLimbs()
424  {
425  Limbs.Clear();
426  if (MainElement?.GetChildElements("limb") is { } childElements)
427  {
428  foreach (var element in childElements)
429  {
430  Limbs.Add(new LimbParams(element, this));
431  }
432  }
433  Limbs = Limbs.OrderBy(l => l.ID).ToList();
434  }
435 
436  protected void CreateJoints()
437  {
438  Joints.Clear();
439  foreach (var element in MainElement.GetChildElements("joint"))
440  {
441  Joints.Add(new JointParams(element, this));
442  }
443  }
444 
445  public bool Deserialize(XElement element = null, bool alsoChildren = true, bool recursive = true)
446  {
447  if (base.Deserialize(element))
448  {
449  if (alsoChildren)
450  {
451  GetAllSubParams().ForEach(p => p.Deserialize(recursive: recursive));
452  }
453  return true;
454  }
455  return false;
456  }
457 
458  public bool Serialize(XElement element = null, bool alsoChildren = true, bool recursive = true)
459  {
460  if (base.Serialize(element))
461  {
462  if (alsoChildren)
463  {
464  GetAllSubParams().ForEach(p => p.Serialize(recursive: recursive));
465  }
466  return true;
467  }
468  return false;
469  }
470 
471 #if CLIENT
472  public void AddToEditor(ParamsEditor editor, bool alsoChildren = true, int space = 0)
473  {
474  base.AddToEditor(editor);
475  if (alsoChildren)
476  {
477  var subParams = GetAllSubParams();
478  foreach (var subParam in subParams)
479  {
480  subParam.AddToEditor(editor, true, space);
481  }
482  }
483  if (space > 0)
484  {
485  new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, space), editor.EditorBox.Content.RectTransform), style: null, color: ParamsEditor.Color)
486  {
487  CanBeFocused = false
488  };
489  }
490  }
491 #endif
492 
493  private bool isVariantScaleApplied;
494  public void TryApplyVariantScale(XDocument variantFile)
495  {
496  if (isVariantScaleApplied) { return; }
497  if (variantFile == null) { return; }
498  if (variantFile.GetRootExcludingOverride() is XElement root)
499  {
500  if ((root.GetChildElement("ragdoll") ?? root.GetChildElement("ragdolls")) is XElement ragdollElement)
501  {
502  float scaleMultiplier = ragdollElement.GetAttributeFloat("scalemultiplier", 1f);
503  JointScale *= scaleMultiplier;
504  LimbScale *= scaleMultiplier;
505  float textureScale = ragdollElement.GetAttributeFloat(nameof(TextureScale), 0f);
506  if (textureScale > 0)
507  {
508  // Override, if defined.
509  TextureScale = textureScale;
510  }
511  float sourceRectScale = ragdollElement.GetAttributeFloat(nameof(SourceRectScale), 0f);
512  if (sourceRectScale > 0)
513  {
514  // Override, if defined.
515  SourceRectScale = sourceRectScale;
516  }
517  }
518  }
519  isVariantScaleApplied = true;
520  }
521 
522  #endregion
523 
524  #region Memento
525  public Memento<RagdollParams> Memento { get; protected set; } = new Memento<RagdollParams>();
526  public void StoreSnapshot()
527  {
528  Serialize();
529  if (doc == null)
530  {
531  DebugConsole.ThrowError("[RagdollParams] The source XML Document is null!");
532  return;
533  }
534  var copy = new RagdollParams
535  {
537  IsLoaded = true,
538  doc = new XDocument(doc),
539  Path = Path
540  };
541  copy.CreateColliders();
542  copy.CreateLimbs();
543  copy.CreateJoints();
544  copy.Deserialize();
545  copy.Serialize();
546  Memento.Store(copy);
547  }
548  public void Undo() => RevertTo(Memento.Undo());
549  public void Redo() => RevertTo(Memento.Redo());
550  public void ClearHistory() => Memento.Clear();
551 
552  private void RevertTo(RagdollParams source)
553  {
554  if (source.MainElement == null)
555  {
556  DebugConsole.ThrowError("[RagdollParams] The source XML Element of the given RagdollParams is null!",
557  contentPackage: source.MainElement?.ContentPackage);
558  return;
559  }
560  Deserialize(source.MainElement, alsoChildren: false);
561  var sourceSubParams = source.GetAllSubParams().ToList();
562  var subParams = GetAllSubParams().ToList();
563  // TODO: cannot currently undo joint/limb deletion.
564  if (sourceSubParams.Count != subParams.Count)
565  {
566  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.",
567  contentPackage: source.MainElement?.ContentPackage);
568  return;
569  }
570  for (int i = 0; i < subParams.Count; i++)
571  {
572  var subSubParams = subParams[i].SubParams;
573  if (subSubParams.Count != sourceSubParams[i].SubParams.Count)
574  {
575  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.",
576  contentPackage: source.MainElement?.ContentPackage);
577  return;
578  }
579  subParams[i].Deserialize(sourceSubParams[i].Element, recursive: false);
580  for (int j = 0; j < subSubParams.Count; j++)
581  {
582  subSubParams[j].Deserialize(sourceSubParams[i].SubParams[j].Element, recursive: false);
583  // Since we cannot use recursion here, we have to go deeper manually, if necessary.
584  }
585  }
586  }
587  #endregion
588 
589  #region Subparams
590  public class JointParams : SubParam
591  {
592  private string name;
594  public override string Name
595  {
596  get
597  {
598  if (string.IsNullOrWhiteSpace(name))
599  {
600  name = GenerateName();
601  }
602  return name;
603  }
604  set
605  {
606  name = value;
607  }
608  }
609 
610  public override string GenerateName() => $"Joint {Limb1} - {Limb2}";
611 
613  public int Limb1 { get; set; }
614 
616  public int Limb2 { get; set; }
617 
621  [Serialize("1.0, 1.0", IsPropertySaveable.Yes, description: "Local position of the joint in the Limb1."), Editable()]
622  public Vector2 Limb1Anchor { get; set; }
623 
627  [Serialize("1.0, 1.0", IsPropertySaveable.Yes, description: "Local position of the joint in the Limb2."), Editable()]
628  public Vector2 Limb2Anchor { get; set; }
629 
631  public bool CanBeSevered { get; set; }
632 
633  [Serialize(0f, IsPropertySaveable.Yes, description:"Default 0 (Can't be severed when the creature is alive). Modifies the severance probability (defined per item/attack) when the character is alive. Currently only affects non-humanoid ragdolls. Also note that if CanBeSevered is false, this property doesn't have any effect."), Editable(MinValueFloat = 0, MaxValueFloat = 10, ValueStep = 0.1f, DecimalCount = 2)]
634  public float SeveranceProbabilityModifier { get; set; }
635 
636  [Serialize("gore", IsPropertySaveable.Yes), Editable]
637  public string BreakSound { get; set; }
638 
640  public bool LimitEnabled { get; set; }
641 
646  public float UpperLimit { get; set; }
647 
652  public float LowerLimit { get; set; }
653 
654  [Serialize(0.25f, IsPropertySaveable.Yes), Editable]
655  public float Stiffness { get; set; }
656 
657  [Serialize(1f, IsPropertySaveable.Yes, description: "CAUTION: Not fully implemented. Only use for limb joints that connect non-animated limbs!"), Editable(DecimalCount = 2)]
658  public float Scale { get; set; }
659 
660  [Serialize(false, IsPropertySaveable.No), Editable(ReadOnly = true)]
661  public bool WeldJoint { get; set; }
662 
663  [Serialize(false, IsPropertySaveable.Yes), Editable]
664  public bool ClockWiseRotation { get; set; }
665 
666  public JointParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
667  }
668 
669  public class LimbParams : SubParam
670  {
674  public readonly List<DecorativeSpriteParams> decorativeSpriteParams = new List<DecorativeSpriteParams>();
675 
676  public AttackParams Attack { get; private set; }
677  public SoundParams Sound { get; private set; }
678  public LightSourceParams LightSource { get; private set; }
679  public List<DamageModifierParams> DamageModifiers { get; private set; } = new List<DamageModifierParams>();
680 
681  private string name;
683  public override string Name
684  {
685  get
686  {
687  if (string.IsNullOrWhiteSpace(name))
688  {
689  name = GenerateName();
690  }
691  return name;
692  }
693  set
694  {
695  name = value;
696  }
697  }
698 
699  public override string GenerateName() => Type != LimbType.None ? $"{Type} ({ID})" : $"Limb {ID}";
700 
702 
703  [Serialize(-1, IsPropertySaveable.Yes), Editable(ReadOnly = true)]
704  public int ID { get; set; }
705 
706  [Serialize(LimbType.None, IsPropertySaveable.Yes, description: "The limb type affects many things, like the animations. Torso or Head are considered as the main limbs. Every character should have at least one Torso or Head."), Editable()]
707  public LimbType Type { get; set; }
708 
709  [Serialize(LimbType.None, IsPropertySaveable.Yes, description: "Secondary limb type to be used for generic purposes. Currently only used in climbing animations."), Editable()]
710  public LimbType SecondaryType { get; set; }
711 
715  public float GetSpriteOrientation() => MathHelper.ToRadians(GetSpriteOrientationInDegrees());
716 
717  public float GetSpriteOrientationInDegrees() => float.IsNaN(SpriteOrientation) ? Ragdoll.SpritesheetOrientation : SpriteOrientation;
718 
720  public string Notes { get; set; }
721 
722  [Serialize(1f, IsPropertySaveable.Yes), Editable(DecimalCount = 2)]
723  public float Scale { get; set; }
724 
725  [Serialize(true, IsPropertySaveable.Yes, description: "Does the limb flip when the character flips?"), Editable()]
726  public bool Flip { get; set; }
727 
728  [Serialize(false, IsPropertySaveable.Yes, description: "Currently only works with non-deformable (normal) sprites."), Editable()]
729  public bool MirrorVertically { get; set; }
730 
731  [Serialize(false, IsPropertySaveable.Yes), Editable]
732  public bool MirrorHorizontally { get; set; }
733 
734  [Serialize(false, IsPropertySaveable.Yes, description: "Disable drawing for this limb."), Editable()]
735  public bool Hide { get; set; }
736 
737  [Serialize(float.NaN, IsPropertySaveable.Yes, description: "Orientation of the sprite as drawn on the spritesheet. " +
738  "Defines the \"forward direction\" of the sprite. Should be configured as the direction pointing outwards from the main limb." +
739  "Incorrectly defined orientations may lead to limbs being rotated incorrectly when e.g. when the character aims or flips to face a different direction. " +
740  "Overrides the value of 'Spritesheet Orientation' for this limb."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)]
741  public float SpriteOrientation { get; set; }
742 
743  [Serialize(LimbType.None, IsPropertySaveable.Yes, description: "If set, the limb sprite will use the same sprite depth as the specified limb. Generally only useful for limbs that get added on the ragdoll on the fly (e.g. extra limbs added via gene splicing).")]
744  public LimbType InheritLimbDepth { get; set; }
745 
746  [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
747  public float SteerForce { get; set; }
748 
749  [Serialize(0f, IsPropertySaveable.Yes, description: "Radius of the collider."), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
750  public float Radius { get; set; }
751 
752  [Serialize(0f, IsPropertySaveable.Yes, description: "Height of the collider."), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
753  public float Height { get; set; }
754 
755  [Serialize(0f, IsPropertySaveable.Yes, description: "Width of the collider."), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
756  public float Width { get; set; }
757 
758  [Serialize(10f, IsPropertySaveable.Yes, description: "The more the density the heavier the limb is."), Editable(MinValueFloat = 0.01f, MaxValueFloat = 100, DecimalCount = 2)]
759  public float Density { get; set; }
760 
761  [Serialize(false, IsPropertySaveable.Yes), Editable]
762  public bool IgnoreCollisions { get; set; }
763 
764  [Serialize(7f, IsPropertySaveable.Yes, description: "Increasing the damping makes the limb stop rotating more quickly."), Editable]
765  public float AngularDamping { get; set; }
766 
767  [Serialize(1f, IsPropertySaveable.Yes, description: "Higher values make AI characters prefer attacking this limb."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 10)]
768  public float AttackPriority { get; set; }
769 
770  [Serialize("0, 0", IsPropertySaveable.Yes, description: "The position which is used to lead the IK chain to the IK goal. Only applicable if the limb is hand or foot."), Editable()]
771  public Vector2 PullPos { get; set; }
772 
773  [Serialize("0, 0", IsPropertySaveable.Yes, description: "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."), Editable()]
774  public Vector2 StepOffset { get; set; }
775 
776  [Serialize(-1, IsPropertySaveable.Yes, description: "The id of the refecence joint. 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. Only applicable if this limb is a foot."), Editable()]
777  public int RefJoint { get; set; }
778 
779  [Serialize("0, 0", IsPropertySaveable.Yes, description: "Relative offset for the mouth position (starting from the center). Only applicable for LimbType.Head. Used for eating."), Editable(DecimalCount = 2, MinValueFloat = -10f, MaxValueFloat = 10f)]
780  public Vector2 MouthPos { get; set; }
781 
782  [Serialize(50f, IsPropertySaveable.Yes, description: "How much torque is applied on the head while updating the eating animations?"), Editable]
783  public float EatTorque { get; set; }
784 
785  [Serialize(2f, IsPropertySaveable.Yes, description: "How strong a linear impulse is applied on the head while updating the eating animations?"), Editable]
786  public float EatImpulse { get; set; }
787 
789  public float ConstantTorque { get; set; }
790 
792  public float ConstantAngle { get; set; }
793 
794  [Serialize(1f, IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = 10)]
795  public float AttackForceMultiplier { get; set; }
796 
797  [Serialize(1f, IsPropertySaveable.Yes, description:"How much damage must be done by the attack in order to be able to cut off the limb. Note that it's evaluated after the damage modifiers."), Editable(DecimalCount = 0, MinValueFloat = 0, MaxValueFloat = 1000)]
798  public float MinSeveranceDamage { get; set; }
799 
800  [Serialize(true, IsPropertySaveable.Yes, description: "Disable if you don't want to allow severing this joint while the creature is alive. Note: Does nothing if the 'Severance Probability Modifier' in the joint settings is 0 (default). Also note that the setting doesn't override certain limitations, e.g. severing the main limb, or legs of a walking creature is not allowed."), Editable]
801  public bool CanBeSeveredAlive { get; set; }
802 
803  //how long it takes for severed limbs to fade out
804  [Serialize(10f, IsPropertySaveable.Yes, "How long it takes for the severed limb to fade out"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1)]
805  public float SeveredFadeOutTime { get; set; } = 10.0f;
806 
807  [Serialize(false, IsPropertySaveable.Yes, description: "Should the tail angle be applied on this limb? If none of the limbs have been defined to use the angle and an angle is defined in the animation parameters, the first tail limb is used."), Editable]
808  public bool ApplyTailAngle { get; set; }
809 
810  [Serialize(false, IsPropertySaveable.Yes, description: "Should this limb be moved like a tail when swimming? Always true for tail limbs. On tails, disable by setting SineFrequencyMultiplier to 0."), Editable]
811  public bool ApplySineMovement { get; set; }
812 
813  [Serialize(1f, IsPropertySaveable.Yes), Editable(ValueStep = 0.1f, DecimalCount = 2)]
814  public float SineFrequencyMultiplier { get; set; }
815 
816  [Serialize(1f, IsPropertySaveable.Yes), Editable(ValueStep = 0.1f, DecimalCount = 2)]
817  public float SineAmplitudeMultiplier { get; set; }
818 
819  [Serialize(0f, IsPropertySaveable.Yes), Editable(0, 100, ValueStep = 1, DecimalCount = 1)]
820  public float BlinkFrequency { get; set; }
821 
822  [Serialize(0.2f, IsPropertySaveable.Yes), Editable(0.01f, 10, ValueStep = 1, DecimalCount = 2)]
823  public float BlinkDurationIn { get; set; }
824 
825  [Serialize(0.5f, IsPropertySaveable.Yes), Editable(0.01f, 10, ValueStep = 1, DecimalCount = 2)]
826  public float BlinkDurationOut { get; set; }
827 
828  [Serialize(0f, IsPropertySaveable.Yes), Editable(0, 10, ValueStep = 1, DecimalCount = 2)]
829  public float BlinkHoldTime { get; set; }
830 
831  [Serialize(0f, IsPropertySaveable.Yes), Editable(-360, 360, ValueStep = 1, DecimalCount = 0)]
832  public float BlinkRotationIn { get; set; }
833 
834  [Serialize(45f, IsPropertySaveable.Yes), Editable(-360, 360, ValueStep = 1, DecimalCount = 0)]
835  public float BlinkRotationOut { get; set; }
836 
838  public float BlinkForce { get; set; }
839 
840  [Serialize(false, IsPropertySaveable.Yes), Editable]
841  public bool OnlyBlinkInWater { get; set; }
842 
843  [Serialize(false, IsPropertySaveable.Yes), Editable]
844  public bool UseTextureOffsetForBlinking { get; set; }
845 
846  [Serialize("0.5, 0.5", IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0f, MaxValueFloat = 1f)]
847  public Vector2 BlinkTextureOffsetIn { get; set; }
848 
849  [Serialize("0.5, 0.5", IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0f, MaxValueFloat = 1f)]
850  public Vector2 BlinkTextureOffsetOut { get; set; }
851 
853  public TransitionMode BlinkTransitionIn { get; private set; }
854 
856  public TransitionMode BlinkTransitionOut { get; private set; }
857 
858  // Non-editable ->
859  // TODO: make read-only
860  [Serialize(0, IsPropertySaveable.Yes)]
861  public int HealthIndex { get; set; }
862 
863  [Serialize(0.3f, IsPropertySaveable.Yes)]
864  public float Friction { get; set; }
865 
866  [Serialize(0.05f, IsPropertySaveable.Yes)]
867  public float Restitution { get; set; }
868 
869  [Serialize(true, IsPropertySaveable.Yes, description: "Can the limb enter submarines? Only valid if the ragdoll's CanEnterSubmarine is set to Partial, otherwise the limb can enter if the ragdoll can."), Editable]
870  public bool CanEnterSubmarine { get; private set; }
871 
872  public LimbParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
873  {
874  var spriteElement = element.GetChildElement("sprite");
875  if (spriteElement != null)
876  {
877  normalSpriteParams = new SpriteParams(spriteElement, ragdoll);
879  }
880  var damagedSpriteElement = element.GetChildElement("damagedsprite");
881  if (damagedSpriteElement != null)
882  {
883  damagedSpriteParams = new SpriteParams(damagedSpriteElement, ragdoll);
884  // Hide the damaged sprite params in the editor for now.
885  //SubParams.Add(damagedSpriteParams);
886  }
887  var deformSpriteElement = element.GetChildElement("deformablesprite");
888  if (deformSpriteElement != null)
889  {
890  deformSpriteParams = new DeformSpriteParams(deformSpriteElement, ragdoll);
892  }
893  foreach (var decorativeSpriteElement in element.GetChildElements("decorativesprite"))
894  {
895  var decorativeParams = new DecorativeSpriteParams(decorativeSpriteElement, ragdoll);
896  decorativeSpriteParams.Add(decorativeParams);
897  SubParams.Add(decorativeParams);
898  }
899  var attackElement = element.GetChildElement("attack");
900  if (attackElement != null)
901  {
902  Attack = new AttackParams(attackElement, ragdoll);
903  SubParams.Add(Attack);
904  }
905  foreach (var damageElement in element.GetChildElements("damagemodifier"))
906  {
907  var damageModifier = new DamageModifierParams(damageElement, ragdoll);
908  DamageModifiers.Add(damageModifier);
909  SubParams.Add(damageModifier);
910  }
911  var soundElement = element.GetChildElement("sound");
912  if (soundElement != null)
913  {
914  Sound = new SoundParams(soundElement, ragdoll);
915  SubParams.Add(Sound);
916  }
917  var lightElement = element.GetChildElement("lightsource");
918  if (lightElement != null)
919  {
920  LightSource = new LightSourceParams(lightElement, ragdoll);
921  SubParams.Add(LightSource);
922  }
923  }
924 
925  public bool AddAttack()
926  {
927  if (Attack != null) { return false; }
928  TryAddSubParam(CreateElement("attack"), (e, c) => new AttackParams(e, c), out AttackParams newAttack);
929  Attack = newAttack;
930  return Attack != null;
931  }
932 
933 
934  public bool AddSound()
935  {
936  if (Sound != null) { return false; }
937  TryAddSubParam(CreateElement("sound"), (e, c) => new SoundParams(e, c), out SoundParams newSound);
938  Sound = newSound;
939  return Sound != null;
940  }
941 
942  public bool AddLight()
943  {
944  if (LightSource != null) { return false; }
945  var lightSourceElement = CreateElement("lightsource",
946  new XElement("lighttexture", new XAttribute("texture", "Content/Lights/pointlight_bright.png")));
947  TryAddSubParam(lightSourceElement, (e, c) => new LightSourceParams(e, c), out LightSourceParams newLightSource);
948  LightSource = newLightSource;
949  return LightSource != null;
950  }
951 
952  public bool AddDamageModifier() => TryAddSubParam(CreateElement("damagemodifier"), (e, c) => new DamageModifierParams(e, c), out _, DamageModifiers);
953 
954  public bool RemoveAttack()
955  {
956  if (RemoveSubParam(Attack))
957  {
958  Attack = null;
959  return true;
960  }
961  return false;
962  }
963 
964  public bool RemoveSound()
965  {
966  if (RemoveSubParam(Sound))
967  {
968  Sound = null;
969  return true;
970  }
971  return false;
972  }
973 
974  public bool RemoveLight()
975  {
976  if (RemoveSubParam(LightSource))
977  {
978  LightSource = null;
979  return true;
980  }
981  return false;
982  }
983 
984  public bool RemoveDamageModifier(DamageModifierParams damageModifier) => RemoveSubParam(damageModifier, DamageModifiers);
985 
986  protected bool TryAddSubParam<T>(ContentXElement element, Func<ContentXElement, RagdollParams, T> constructor, out T subParam, IList<T> collection = null, Func<IList<T>, bool> filter = null) where T : SubParam
987  {
988  subParam = constructor(element, Ragdoll);
989  if (collection != null && filter != null)
990  {
991  if (filter(collection)) { return false; }
992  }
993  Element.Add(element);
994  SubParams.Add(subParam);
995  collection?.Add(subParam);
996  return subParam != null;
997  }
998 
999  protected bool RemoveSubParam<T>(T subParam, IList<T> collection = null) where T : SubParam
1000  {
1001  if (subParam == null || subParam.Element == null || subParam.Element.Parent == null) { return false; }
1002  if (collection != null && !collection.Contains(subParam)) { return false; }
1003  if (!SubParams.Contains(subParam)) { return false; }
1004  collection?.Remove(subParam);
1005  SubParams.Remove(subParam);
1006  subParam.Element.Remove();
1007  return true;
1008  }
1009  }
1010 
1012  {
1013  public DecorativeSpriteParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1014  {
1015 #if CLIENT
1016  DecorativeSprite = new DecorativeSprite(element);
1017 #endif
1018  }
1019 
1020 #if CLIENT
1021  public DecorativeSprite DecorativeSprite { get; private set; }
1022 
1023  public override bool Deserialize(ContentXElement element = null, bool recursive = true)
1024  {
1025  base.Deserialize(element, recursive);
1027  return SerializableProperties != null;
1028  }
1029 
1030  public override bool Serialize(ContentXElement element = null, bool recursive = true)
1031  {
1032  base.Serialize(element, recursive);
1034  return true;
1035  }
1036 
1037  public override void Reset()
1038  {
1039  base.Reset();
1041  }
1042 #endif
1043  }
1044 
1046  {
1047  public DeformationParams Deformation { get; private set; }
1048 
1049  public DeformSpriteParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1050  {
1051  Deformation = new DeformationParams(element, ragdoll);
1052  SubParams.Add(Deformation);
1053  }
1054  }
1055 
1056  public class SpriteParams : SubParam
1057  {
1058  [Serialize("0, 0, 0, 0", IsPropertySaveable.Yes), Editable]
1059  public Rectangle SourceRect { get; set; }
1060 
1061  [Serialize("0.5, 0.5", IsPropertySaveable.Yes, description: "The origin of the sprite relative to the collider."), Editable(DecimalCount = 3)]
1062  public Vector2 Origin { get; set; }
1063 
1064  [Serialize(0f, IsPropertySaveable.Yes, description: "The Z-depth of the limb relative to other limbs of the same character. 1 is front, 0 is behind."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 3)]
1065  public float Depth { get; set; }
1066 
1067  [Serialize("", IsPropertySaveable.Yes), Editable()]
1068  public string Texture { get; set; }
1069 
1070  [Serialize(false, IsPropertySaveable.Yes), Editable()]
1071  public bool IgnoreTint { get; set; }
1072 
1073  [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable()]
1074  public Color Color { get; set; }
1075 
1076  [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes, description: "Target color when the character is dead."), Editable()]
1077  public Color DeadColor { get; set; }
1078 
1079  [Serialize(0f, IsPropertySaveable.Yes, "How long it takes to fade into the dead color? 0 = Not applied."), Editable(DecimalCount = 1, MinValueFloat = 0, MaxValueFloat = 10)]
1080  public float DeadColorTime { get; set; }
1081 
1082  public override string Name => "Sprite";
1083 
1084  public SpriteParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
1085 
1086  public string GetTexturePath() => string.IsNullOrWhiteSpace(Texture) ? Ragdoll.Texture : Texture;
1087  }
1088 
1090  {
1091  public DeformationParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1092  {
1093 #if CLIENT
1094  Deformations = new Dictionary<SpriteDeformationParams, XElement>();
1095  foreach (var deformationElement in element.GetChildElements("spritedeformation"))
1096  {
1097  string typeName = deformationElement.GetAttributeString("type", null) ?? deformationElement.GetAttributeString("typename", string.Empty);
1098  SpriteDeformationParams deformation = null;
1099  switch (typeName.ToLowerInvariant())
1100  {
1101  case "inflate":
1102  deformation = new InflateParams(deformationElement);
1103  break;
1104  case "custom":
1105  deformation = new CustomDeformationParams(deformationElement);
1106  break;
1107  case "noise":
1108  deformation = new NoiseDeformationParams(deformationElement);
1109  break;
1110  case "jointbend":
1111  case "bendjoint":
1112  deformation = new JointBendDeformationParams(deformationElement);
1113  break;
1114  case "reacttotriggerers":
1115  deformation = new PositionalDeformationParams(deformationElement);
1116  break;
1117  default:
1118  DebugConsole.ThrowError($"SpriteDeformationParams not implemented: '{typeName}'",
1119  contentPackage: element.ContentPackage);
1120  break;
1121  }
1122  if (deformation != null)
1123  {
1124  deformation.Type = typeName;
1125  }
1126  Deformations.Add(deformation, deformationElement);
1127  }
1128 #endif
1129  }
1130 
1131 #if CLIENT
1132  public Dictionary<SpriteDeformationParams, XElement> Deformations { get; private set; }
1133 
1134  public override bool Deserialize(ContentXElement element = null, bool recursive = true)
1135  {
1136  base.Deserialize(element, recursive);
1137  Deformations.ForEach(d => d.Key.SerializableProperties = SerializableProperty.DeserializeProperties(d.Key, d.Value));
1138  return SerializableProperties != null;
1139  }
1140 
1141  public override bool Serialize(ContentXElement element = null, bool recursive = true)
1142  {
1143  base.Serialize(element, recursive);
1144  Deformations.ForEach(d => SerializableProperty.SerializeProperties(d.Key, d.Value));
1145  return true;
1146  }
1147 
1148  public override void Reset()
1149  {
1150  base.Reset();
1151  Deformations.ForEach(d => d.Key.SerializableProperties = SerializableProperty.DeserializeProperties(d.Key, d.Value));
1152  }
1153 #endif
1154  }
1155 
1156  public class ColliderParams : SubParam
1157  {
1158  private string name;
1160  public override string Name
1161  {
1162  get
1163  {
1164  if (string.IsNullOrWhiteSpace(name))
1165  {
1166  name = GenerateName();
1167  }
1168  return name;
1169  }
1170  set
1171  {
1172  name = value;
1173  }
1174  }
1175 
1176  [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
1177  public float Radius { get; set; }
1178 
1179  [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
1180  public float Height { get; set; }
1181 
1182  [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
1183  public float Width { get; set; }
1184 
1186  public BodyType BodyType { get; set; }
1187 
1188  public ColliderParams(ContentXElement element, RagdollParams ragdoll, string name = null) : base(element, ragdoll)
1189  {
1190  Name = name;
1191  }
1192  }
1193 
1195  {
1196  public class LightTexture : SubParam
1197  {
1198  public override string Name => "Light Texture";
1199 
1200  [Serialize("Content/Lights/pointlight_bright.png", IsPropertySaveable.Yes), Editable]
1201  public string Texture { get; private set; }
1202 
1203  [Serialize("0.5, 0.5", IsPropertySaveable.Yes), Editable(DecimalCount = 2)]
1204  public Vector2 Origin { get; set; }
1205 
1206  [Serialize("1.0, 1.0", IsPropertySaveable.Yes), Editable(DecimalCount = 2)]
1207  public Vector2 Size { get; set; }
1208 
1209  public LightTexture(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
1210  }
1211 
1212  public LightTexture Texture { get; private set; }
1213 
1214 #if CLIENT
1215  public Lights.LightSourceParams LightSource { get; private set; }
1216 #endif
1217 
1218  public LightSourceParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1219  {
1220 #if CLIENT
1221  LightSource = new Lights.LightSourceParams(element);
1222 #endif
1223  var lightTextureElement = element.GetChildElement("lighttexture");
1224  if (lightTextureElement != null)
1225  {
1226  Texture = new LightTexture(lightTextureElement, ragdoll);
1227  SubParams.Add(Texture);
1228  }
1229  }
1230 
1231 #if CLIENT
1232  public override bool Deserialize(ContentXElement element = null, bool recursive = true)
1233  {
1234  base.Deserialize(element, recursive);
1235  LightSource.Deserialize(element ?? Element);
1236  return SerializableProperties != null;
1237  }
1238 
1239  public override bool Serialize(ContentXElement element = null, bool recursive = true)
1240  {
1241  base.Serialize(element, recursive);
1242  LightSource.Serialize(element ?? Element);
1243  return true;
1244  }
1245 
1246  public override void Reset()
1247  {
1248  base.Reset();
1250  }
1251 #endif
1252  }
1253 
1254  // TODO: conditionals?
1255  public class AttackParams : SubParam
1256  {
1257  public Attack Attack { get; private set; }
1258 
1259  public AttackParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1260  {
1261  Attack = new Attack(element, ragdoll.SpeciesName.Value);
1262  }
1263 
1264  public override bool Deserialize(ContentXElement element = null, bool recursive = true)
1265  {
1266  base.Deserialize(element, recursive);
1267  Attack.Deserialize(element ?? Element, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
1268  return SerializableProperties != null;
1269  }
1270 
1271  public override bool Serialize(ContentXElement element = null, bool recursive = true)
1272  {
1273  base.Serialize(element, recursive);
1274  Attack.Serialize(element ?? Element);
1275  return true;
1276  }
1277 
1278  public override void Reset()
1279  {
1280  base.Reset();
1281  Attack.Deserialize(OriginalElement, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
1282  Attack.ReloadAfflictions(OriginalElement, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
1283  }
1284 
1285  public bool AddNewAffliction()
1286  {
1287  Serialize();
1288  var subElement = CreateElement("affliction",
1289  new XAttribute("identifier", "internaldamage"),
1290  new XAttribute("strength", 0f),
1291  new XAttribute("probability", 1.0f));
1292  Element.Add(subElement);
1293  Attack.ReloadAfflictions(Element, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
1294  Serialize();
1295  return true;
1296  }
1297 
1298  public bool RemoveAffliction(XElement affliction)
1299  {
1300  Serialize();
1301  affliction.Remove();
1302  Attack.ReloadAfflictions(Element, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
1303  return Serialize();
1304  }
1305  }
1306 
1308  {
1309  public DamageModifier DamageModifier { get; private set; }
1310 
1311  public DamageModifierParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1312  {
1313  DamageModifier = new DamageModifier(element, ragdoll.SpeciesName.Value);
1314  }
1315 
1316  public override bool Deserialize(ContentXElement element = null, bool recursive = true)
1317  {
1318  base.Deserialize(element, recursive);
1319  DamageModifier.Deserialize(element ?? Element);
1320  return SerializableProperties != null;
1321  }
1322 
1323  public override bool Serialize(ContentXElement element = null, bool recursive = true)
1324  {
1325  base.Serialize(element, recursive);
1326  DamageModifier.Serialize(element ?? Element);
1327  return true;
1328  }
1329 
1330  public override void Reset()
1331  {
1332  base.Reset();
1334  }
1335  }
1336 
1337  public class SoundParams : SubParam
1338  {
1339  public override string Name => "Sound";
1340 
1342  public string Tag { get; private set; }
1343 
1344  public SoundParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
1345  }
1346 
1347  public abstract class SubParam : ISerializableEntity
1348  {
1349  public virtual string Name { get; set; }
1350  public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
1351  public ContentXElement Element { get; set; }
1352  public ContentXElement OriginalElement { get; protected set; }
1353  public List<SubParam> SubParams { get; set; } = new List<SubParam>();
1354  public RagdollParams Ragdoll { get; private set; }
1355 
1356  public virtual string GenerateName() => Element.Name.ToString();
1357 
1358  protected ContentXElement CreateElement(string name, params object[] attrs)
1359  => new XElement(name, attrs).FromPackage(Element.ContentPackage);
1360 
1361  public SubParam(ContentXElement element, RagdollParams ragdoll)
1362  {
1363  Element = element;
1364  OriginalElement = new ContentXElement(element.ContentPackage, element);
1365  Ragdoll = ragdoll;
1367  }
1368 
1369  public virtual bool Deserialize(ContentXElement element = null, bool recursive = true)
1370  {
1371  element ??= Element;
1373  if (recursive)
1374  {
1375  SubParams.ForEach(sp => sp.Deserialize(recursive: true));
1376  }
1377  return SerializableProperties != null;
1378  }
1379 
1380  public virtual bool Serialize(ContentXElement element = null, bool recursive = true)
1381  {
1382  element ??= Element;
1383  SerializableProperty.SerializeProperties(this, element, true);
1384  if (recursive)
1385  {
1386  SubParams.ForEach(sp => sp.Serialize(recursive: true));
1387  }
1388  return true;
1389  }
1390 
1392  {
1394  SubParams.ForEach(sp => sp.SetCurrentElementAsOriginalElement());
1395  }
1396 
1397  public virtual void Reset()
1398  {
1399  // Don't use recursion, because the reset method might be overriden
1400  Deserialize(OriginalElement, recursive: false);
1401  SubParams.ForEach(sp => sp.Reset());
1402  }
1403 
1404 #if CLIENT
1406  public Dictionary<Affliction, SerializableEntityEditor> AfflictionEditors { get; private set; }
1407  public virtual void AddToEditor(ParamsEditor editor, bool recursive = true, int space = 0)
1408  {
1409  SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1410  if (this is DecorativeSpriteParams decSpriteParams)
1411  {
1412  new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, decSpriteParams.DecorativeSprite, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1413  }
1414  else if (this is DeformSpriteParams deformSpriteParams)
1415  {
1416  foreach (var deformation in deformSpriteParams.Deformation.Deformations.Keys)
1417  {
1418  new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, deformation, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1419  }
1420  }
1421  else if (this is AttackParams attackParams)
1422  {
1423  SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, attackParams.Attack, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1424  if (AfflictionEditors == null)
1425  {
1426  AfflictionEditors = new Dictionary<Affliction, SerializableEntityEditor>();
1427  }
1428  else
1429  {
1430  AfflictionEditors.Clear();
1431  }
1432  foreach (var affliction in attackParams.Attack.Afflictions.Keys)
1433  {
1434  var afflictionEditor = new SerializableEntityEditor(SerializableEntityEditor.RectTransform, affliction, inGame: false, showName: true);
1435  AfflictionEditors.Add(affliction, afflictionEditor);
1437  }
1438  }
1439  else if (this is LightSourceParams lightParams)
1440  {
1441  SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, lightParams.LightSource, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1442  }
1443  else if (this is DamageModifierParams damageModifierParams)
1444  {
1445  SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, damageModifierParams.DamageModifier, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1446  }
1447  if (recursive)
1448  {
1449  SubParams.ForEach(sp => sp.AddToEditor(editor, true));
1450  }
1451  if (space > 0)
1452  {
1453  new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, space), editor.EditorBox.Content.RectTransform), style: null, color: new Color(20, 20, 20, 255))
1454  {
1455  CanBeFocused = false
1456  };
1457  }
1458  }
1459 #endif
1460  }
1461  #endregion
1462  }
1463 }
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
void Deserialize(ContentXElement element, string parentDebugName)
void ReloadAfflictions(ContentXElement element, string parentDebugName)
Contains character data that should be editable in the character editor.
static CharacterPrefab Find(Predicate< CharacterPrefab > predicate)
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
ContentXElement ConfigElement
static readonly PrefabCollection< CharacterPrefab > Prefabs
readonly ContentPath Path
Definition: ContentFile.cs:137
static ContentPath FromRaw(string? rawValue)
string???????????? Value
Definition: ContentPath.cs:27
void Add(ContentXElement elem)
ContentPackage? ContentPackage
IEnumerable< ContentXElement > GetChildElements(string name)
ContentXElement? GetChildElement(string name)
Dictionary< Identifier, SerializableProperty > SerializableProperties
ContentXElement OriginalElement
virtual ContentXElement? MainElement
static FishRagdollParams GetDefaultRagdollParams(Character character)
virtual Rectangle Rect
RectTransform RectTransform
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:42
static HumanRagdollParams GetDefaultRagdollParams(Character character)
void Store(T newState)
Definition: Memento.cs:25
readonly ContentFile ContentFile
Definition: Prefab.cs:35
override bool Serialize(ContentXElement element=null, bool recursive=true)
bool RemoveAffliction(XElement affliction)
AttackParams(ContentXElement element, RagdollParams ragdoll)
override bool Deserialize(ContentXElement element=null, bool recursive=true)
ColliderParams(ContentXElement element, RagdollParams ragdoll, string name=null)
DamageModifierParams(ContentXElement element, RagdollParams ragdoll)
override bool Deserialize(ContentXElement element=null, bool recursive=true)
override bool Serialize(ContentXElement element=null, bool recursive=true)
DecorativeSpriteParams(ContentXElement element, RagdollParams ragdoll)
override bool Deserialize(ContentXElement element=null, bool recursive=true)
override bool Serialize(ContentXElement element=null, bool recursive=true)
DeformSpriteParams(ContentXElement element, RagdollParams ragdoll)
DeformationParams(ContentXElement element, RagdollParams ragdoll)
Dictionary< SpriteDeformationParams, XElement > Deformations
override bool Serialize(ContentXElement element=null, bool recursive=true)
override bool Deserialize(ContentXElement element=null, bool recursive=true)
Vector2 Limb1Anchor
Should be converted to sim units.
JointParams(ContentXElement element, RagdollParams ragdoll)
Vector2 Limb2Anchor
Should be converted to sim units.
LightTexture(ContentXElement element, RagdollParams ragdoll)
override bool Deserialize(ContentXElement element=null, bool recursive=true)
LightSourceParams(ContentXElement element, RagdollParams ragdoll)
override bool Serialize(ContentXElement element=null, bool recursive=true)
LimbParams(ContentXElement element, RagdollParams ragdoll)
readonly DeformSpriteParams deformSpriteParams
List< DamageModifierParams > DamageModifiers
readonly List< DecorativeSpriteParams > decorativeSpriteParams
readonly SpriteParams damagedSpriteParams
readonly SpriteParams normalSpriteParams
float GetSpriteOrientation()
The orientation of the sprite as drawn on the sprite sheet (in radians).
bool RemoveDamageModifier(DamageModifierParams damageModifier)
bool RemoveSubParam< T >(T subParam, IList< T > collection=null)
bool TryAddSubParam< T >(ContentXElement element, Func< ContentXElement, RagdollParams, T > constructor, out T subParam, IList< T > collection=null, Func< IList< T >, bool > filter=null)
SoundParams(ContentXElement element, RagdollParams ragdoll)
SpriteParams(ContentXElement element, RagdollParams ragdoll)
virtual bool Serialize(ContentXElement element=null, bool recursive=true)
virtual void AddToEditor(ParamsEditor editor, bool recursive=true, int space=0)
virtual void SetCurrentElementAsOriginalElement()
ContentXElement CreateElement(string name, params object[] attrs)
Dictionary< Identifier, SerializableProperty > SerializableProperties
Dictionary< Affliction, SerializableEntityEditor > AfflictionEditors
SubParam(ContentXElement element, RagdollParams ragdoll)
virtual bool Deserialize(ContentXElement element=null, bool recursive=true)
SerializableEntityEditor SerializableEntityEditor
override bool Reset(bool forceReload=false)
Resets the current properties to the xml (stored in memory). Force reload reloads the file from disk.
float SourceRectScale
Multiplies both the position and the size of the source rects. Used for scaling the textures when we ...
static T CreateDefault< T >(string fullPath, Identifier speciesName, XElement mainElement)
Creates a default ragdoll for the species using a predefined configuration. Note: Use only to create ...
bool Serialize(XElement element=null, bool alsoChildren=true, bool recursive=true)
override void UpdatePath(ContentPath fullPath)
IEnumerable< SubParam > GetAllSubParams()
static string GetDefaultFile(Identifier speciesName, ContentPackage contentPackage=null)
void TryApplyVariantScale(XDocument variantFile)
bool Save(string fileNameWithoutExtension=null)
void AddToEditor(ParamsEditor editor, bool alsoChildren=true, int space=0)
static T GetDefaultRagdollParams< T >(Character character)
static string GetFolder(Identifier speciesName, ContentPackage contentPackage=null)
bool Deserialize(XElement element=null, bool alsoChildren=true, bool recursive=true)
static void ClearCache()
List< ColliderParams > Colliders
static string GetDefaultFileName(Identifier speciesName)
static T GetRagdollParams< T >(Identifier speciesName, Identifier ragdollSpecies, Either< string, ContentPath > file, ContentPackage contentPackage)
List< LimbParams > Limbs
float TextureScale
Can be used for scaling the textures without having to readjust the entire ragdoll....
void Apply()
Applies the current properties to the xml definition without saving to file.
bool Load(ContentPath file, Identifier speciesName)
List< JointParams > Joints
void AddCustomContent(GUIComponent component, int childIndex)
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)
static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault=false, bool ignoreEditable=false)
@ Partial
The ragdoll's limbs can enter the sub, but the collider can't. Can be used to e.g....
@ True
Can fully enter a submarine. Make sure to only allow this on small/medium sized creatures that can re...
@ False
No part of the ragdoll can go inside a submarine
TransitionMode
Definition: Enums.cs:6