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]
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 
712  public float GetSpriteOrientation() => MathHelper.ToRadians(GetSpriteOrientationInDegrees());
713 
714  public float GetSpriteOrientationInDegrees() => float.IsNaN(SpriteOrientation) ? Ragdoll.SpritesheetOrientation : SpriteOrientation;
715 
717  public string Notes { get; set; }
718 
719  [Serialize(1f, IsPropertySaveable.Yes), Editable(DecimalCount = 2)]
720  public float Scale { get; set; }
721 
722  [Serialize(true, IsPropertySaveable.Yes, description: "Does the limb flip when the character flips?"), Editable()]
723  public bool Flip { get; set; }
724 
725  [Serialize(false, IsPropertySaveable.Yes, description: "Currently only works with non-deformable (normal) sprites."), Editable()]
726  public bool MirrorVertically { get; set; }
727 
728  [Serialize(false, IsPropertySaveable.Yes), Editable]
729  public bool MirrorHorizontally { get; set; }
730 
731  [Serialize(false, IsPropertySaveable.Yes, description: "Disable drawing for this limb."), Editable()]
732  public bool Hide { get; set; }
733 
734  [Serialize(float.NaN, IsPropertySaveable.Yes, description: "Orientation of the sprite as drawn on the spritesheet. " +
735  "Defines the \"forward direction\" of the sprite. Should be configured as the direction pointing outwards from the main limb." +
736  "Incorrectly defined orientations may lead to limbs being rotated incorrectly when e.g. when the character aims or flips to face a different direction. " +
737  "Overrides the value of 'Spritesheet Orientation' for this limb."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)]
738  public float SpriteOrientation { get; set; }
739 
740  [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).")]
741  public LimbType InheritLimbDepth { get; set; }
742 
743  [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
744  public float SteerForce { get; set; }
745 
746  [Serialize(0f, IsPropertySaveable.Yes, description: "Radius of the collider."), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
747  public float Radius { get; set; }
748 
749  [Serialize(0f, IsPropertySaveable.Yes, description: "Height of the collider."), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
750  public float Height { get; set; }
751 
752  [Serialize(0f, IsPropertySaveable.Yes, description: "Width of the collider."), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
753  public float Width { get; set; }
754 
755  [Serialize(10f, IsPropertySaveable.Yes, description: "The more the density the heavier the limb is."), Editable(MinValueFloat = 0.01f, MaxValueFloat = 100, DecimalCount = 2)]
756  public float Density { get; set; }
757 
758  [Serialize(false, IsPropertySaveable.Yes), Editable]
759  public bool IgnoreCollisions { get; set; }
760 
761  [Serialize(7f, IsPropertySaveable.Yes, description: "Increasing the damping makes the limb stop rotating more quickly."), Editable]
762  public float AngularDamping { get; set; }
763 
764  [Serialize(1f, IsPropertySaveable.Yes, description: "Higher values make AI characters prefer attacking this limb."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 10)]
765  public float AttackPriority { get; set; }
766 
767  [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()]
768  public Vector2 PullPos { get; set; }
769 
770  [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()]
771  public Vector2 StepOffset { get; set; }
772 
773  [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()]
774  public int RefJoint { get; set; }
775 
776  [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)]
777  public Vector2 MouthPos { get; set; }
778 
780  public float ConstantTorque { get; set; }
781 
783  public float ConstantAngle { get; set; }
784 
785  [Serialize(1f, IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = 10)]
786  public float AttackForceMultiplier { get; set; }
787 
788  [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)]
789  public float MinSeveranceDamage { get; set; }
790 
791  [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]
792  public bool CanBeSeveredAlive { get; set; }
793 
794  //how long it takes for severed limbs to fade out
795  [Serialize(10f, IsPropertySaveable.Yes, "How long it takes for the severed limb to fade out"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1)]
796  public float SeveredFadeOutTime { get; set; } = 10.0f;
797 
798  [Serialize(false, IsPropertySaveable.Yes, description: "Only applied when the limb is of type Tail. If none of the tails have been defined to use the angle and an angle is defined in the animation parameters, the first tail limb is used."), Editable]
799  public bool ApplyTailAngle { get; set; }
800 
801  [Serialize(1f, IsPropertySaveable.Yes), Editable(ValueStep = 0.1f, DecimalCount = 2)]
802  public float SineFrequencyMultiplier { get; set; }
803 
804  [Serialize(1f, IsPropertySaveable.Yes), Editable(ValueStep = 0.1f, DecimalCount = 2)]
805  public float SineAmplitudeMultiplier { get; set; }
806 
807  [Serialize(0f, IsPropertySaveable.Yes), Editable(0, 100, ValueStep = 1, DecimalCount = 1)]
808  public float BlinkFrequency { get; set; }
809 
810  [Serialize(0.2f, IsPropertySaveable.Yes), Editable(0.01f, 10, ValueStep = 1, DecimalCount = 2)]
811  public float BlinkDurationIn { get; set; }
812 
813  [Serialize(0.5f, IsPropertySaveable.Yes), Editable(0.01f, 10, ValueStep = 1, DecimalCount = 2)]
814  public float BlinkDurationOut { get; set; }
815 
816  [Serialize(0f, IsPropertySaveable.Yes), Editable(0, 10, ValueStep = 1, DecimalCount = 2)]
817  public float BlinkHoldTime { get; set; }
818 
819  [Serialize(0f, IsPropertySaveable.Yes), Editable(-360, 360, ValueStep = 1, DecimalCount = 0)]
820  public float BlinkRotationIn { get; set; }
821 
822  [Serialize(45f, IsPropertySaveable.Yes), Editable(-360, 360, ValueStep = 1, DecimalCount = 0)]
823  public float BlinkRotationOut { get; set; }
824 
826  public float BlinkForce { get; set; }
827 
828  [Serialize(false, IsPropertySaveable.Yes), Editable]
829  public bool OnlyBlinkInWater { get; set; }
830 
831  [Serialize(false, IsPropertySaveable.Yes), Editable]
832  public bool UseTextureOffsetForBlinking { get; set; }
833 
834  [Serialize("0.5, 0.5", IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0f, MaxValueFloat = 1f)]
835  public Vector2 BlinkTextureOffsetIn { get; set; }
836 
837  [Serialize("0.5, 0.5", IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0f, MaxValueFloat = 1f)]
838  public Vector2 BlinkTextureOffsetOut { get; set; }
839 
841  public TransitionMode BlinkTransitionIn { get; private set; }
842 
844  public TransitionMode BlinkTransitionOut { get; private set; }
845 
846  // Non-editable ->
847  // TODO: make read-only
848  [Serialize(0, IsPropertySaveable.Yes)]
849  public int HealthIndex { get; set; }
850 
851  [Serialize(0.3f, IsPropertySaveable.Yes)]
852  public float Friction { get; set; }
853 
854  [Serialize(0.05f, IsPropertySaveable.Yes)]
855  public float Restitution { get; set; }
856 
857  [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]
858  public bool CanEnterSubmarine { get; private set; }
859 
860  public LimbParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
861  {
862  var spriteElement = element.GetChildElement("sprite");
863  if (spriteElement != null)
864  {
865  normalSpriteParams = new SpriteParams(spriteElement, ragdoll);
867  }
868  var damagedSpriteElement = element.GetChildElement("damagedsprite");
869  if (damagedSpriteElement != null)
870  {
871  damagedSpriteParams = new SpriteParams(damagedSpriteElement, ragdoll);
872  // Hide the damaged sprite params in the editor for now.
873  //SubParams.Add(damagedSpriteParams);
874  }
875  var deformSpriteElement = element.GetChildElement("deformablesprite");
876  if (deformSpriteElement != null)
877  {
878  deformSpriteParams = new DeformSpriteParams(deformSpriteElement, ragdoll);
880  }
881  foreach (var decorativeSpriteElement in element.GetChildElements("decorativesprite"))
882  {
883  var decorativeParams = new DecorativeSpriteParams(decorativeSpriteElement, ragdoll);
884  decorativeSpriteParams.Add(decorativeParams);
885  SubParams.Add(decorativeParams);
886  }
887  var attackElement = element.GetChildElement("attack");
888  if (attackElement != null)
889  {
890  Attack = new AttackParams(attackElement, ragdoll);
891  SubParams.Add(Attack);
892  }
893  foreach (var damageElement in element.GetChildElements("damagemodifier"))
894  {
895  var damageModifier = new DamageModifierParams(damageElement, ragdoll);
896  DamageModifiers.Add(damageModifier);
897  SubParams.Add(damageModifier);
898  }
899  var soundElement = element.GetChildElement("sound");
900  if (soundElement != null)
901  {
902  Sound = new SoundParams(soundElement, ragdoll);
903  SubParams.Add(Sound);
904  }
905  var lightElement = element.GetChildElement("lightsource");
906  if (lightElement != null)
907  {
908  LightSource = new LightSourceParams(lightElement, ragdoll);
909  SubParams.Add(LightSource);
910  }
911  }
912 
913  public bool AddAttack()
914  {
915  if (Attack != null) { return false; }
916  TryAddSubParam(CreateElement("attack"), (e, c) => new AttackParams(e, c), out AttackParams newAttack);
917  Attack = newAttack;
918  return Attack != null;
919  }
920 
921 
922  public bool AddSound()
923  {
924  if (Sound != null) { return false; }
925  TryAddSubParam(CreateElement("sound"), (e, c) => new SoundParams(e, c), out SoundParams newSound);
926  Sound = newSound;
927  return Sound != null;
928  }
929 
930  public bool AddLight()
931  {
932  if (LightSource != null) { return false; }
933  var lightSourceElement = CreateElement("lightsource",
934  new XElement("lighttexture", new XAttribute("texture", "Content/Lights/pointlight_bright.png")));
935  TryAddSubParam(lightSourceElement, (e, c) => new LightSourceParams(e, c), out LightSourceParams newLightSource);
936  LightSource = newLightSource;
937  return LightSource != null;
938  }
939 
940  public bool AddDamageModifier() => TryAddSubParam(CreateElement("damagemodifier"), (e, c) => new DamageModifierParams(e, c), out _, DamageModifiers);
941 
942  public bool RemoveAttack()
943  {
944  if (RemoveSubParam(Attack))
945  {
946  Attack = null;
947  return true;
948  }
949  return false;
950  }
951 
952  public bool RemoveSound()
953  {
954  if (RemoveSubParam(Sound))
955  {
956  Sound = null;
957  return true;
958  }
959  return false;
960  }
961 
962  public bool RemoveLight()
963  {
964  if (RemoveSubParam(LightSource))
965  {
966  LightSource = null;
967  return true;
968  }
969  return false;
970  }
971 
972  public bool RemoveDamageModifier(DamageModifierParams damageModifier) => RemoveSubParam(damageModifier, DamageModifiers);
973 
974  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
975  {
976  subParam = constructor(element, Ragdoll);
977  if (collection != null && filter != null)
978  {
979  if (filter(collection)) { return false; }
980  }
981  Element.Add(element);
982  SubParams.Add(subParam);
983  collection?.Add(subParam);
984  return subParam != null;
985  }
986 
987  protected bool RemoveSubParam<T>(T subParam, IList<T> collection = null) where T : SubParam
988  {
989  if (subParam == null || subParam.Element == null || subParam.Element.Parent == null) { return false; }
990  if (collection != null && !collection.Contains(subParam)) { return false; }
991  if (!SubParams.Contains(subParam)) { return false; }
992  collection?.Remove(subParam);
993  SubParams.Remove(subParam);
994  subParam.Element.Remove();
995  return true;
996  }
997  }
998 
1000  {
1001  public DecorativeSpriteParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1002  {
1003 #if CLIENT
1004  DecorativeSprite = new DecorativeSprite(element);
1005 #endif
1006  }
1007 
1008 #if CLIENT
1009  public DecorativeSprite DecorativeSprite { get; private set; }
1010 
1011  public override bool Deserialize(ContentXElement element = null, bool recursive = true)
1012  {
1013  base.Deserialize(element, recursive);
1015  return SerializableProperties != null;
1016  }
1017 
1018  public override bool Serialize(ContentXElement element = null, bool recursive = true)
1019  {
1020  base.Serialize(element, recursive);
1022  return true;
1023  }
1024 
1025  public override void Reset()
1026  {
1027  base.Reset();
1029  }
1030 #endif
1031  }
1032 
1034  {
1035  public DeformationParams Deformation { get; private set; }
1036 
1037  public DeformSpriteParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1038  {
1039  Deformation = new DeformationParams(element, ragdoll);
1040  SubParams.Add(Deformation);
1041  }
1042  }
1043 
1044  public class SpriteParams : SubParam
1045  {
1046  [Serialize("0, 0, 0, 0", IsPropertySaveable.Yes), Editable]
1047  public Rectangle SourceRect { get; set; }
1048 
1049  [Serialize("0.5, 0.5", IsPropertySaveable.Yes, description: "The origin of the sprite relative to the collider."), Editable(DecimalCount = 3)]
1050  public Vector2 Origin { get; set; }
1051 
1052  [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)]
1053  public float Depth { get; set; }
1054 
1055  [Serialize("", IsPropertySaveable.Yes), Editable()]
1056  public string Texture { get; set; }
1057 
1058  [Serialize(false, IsPropertySaveable.Yes), Editable()]
1059  public bool IgnoreTint { get; set; }
1060 
1061  [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable()]
1062  public Color Color { get; set; }
1063 
1064  [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes, description: "Target color when the character is dead."), Editable()]
1065  public Color DeadColor { get; set; }
1066 
1067  [Serialize(0f, IsPropertySaveable.Yes, "How long it takes to fade into the dead color? 0 = Not applied."), Editable(DecimalCount = 1, MinValueFloat = 0, MaxValueFloat = 10)]
1068  public float DeadColorTime { get; set; }
1069 
1070  public override string Name => "Sprite";
1071 
1072  public SpriteParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
1073 
1074  public string GetTexturePath() => string.IsNullOrWhiteSpace(Texture) ? Ragdoll.Texture : Texture;
1075  }
1076 
1078  {
1079  public DeformationParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1080  {
1081 #if CLIENT
1082  Deformations = new Dictionary<SpriteDeformationParams, XElement>();
1083  foreach (var deformationElement in element.GetChildElements("spritedeformation"))
1084  {
1085  string typeName = deformationElement.GetAttributeString("type", null) ?? deformationElement.GetAttributeString("typename", string.Empty);
1086  SpriteDeformationParams deformation = null;
1087  switch (typeName.ToLowerInvariant())
1088  {
1089  case "inflate":
1090  deformation = new InflateParams(deformationElement);
1091  break;
1092  case "custom":
1093  deformation = new CustomDeformationParams(deformationElement);
1094  break;
1095  case "noise":
1096  deformation = new NoiseDeformationParams(deformationElement);
1097  break;
1098  case "jointbend":
1099  case "bendjoint":
1100  deformation = new JointBendDeformationParams(deformationElement);
1101  break;
1102  case "reacttotriggerers":
1103  deformation = new PositionalDeformationParams(deformationElement);
1104  break;
1105  default:
1106  DebugConsole.ThrowError($"SpriteDeformationParams not implemented: '{typeName}'",
1107  contentPackage: element.ContentPackage);
1108  break;
1109  }
1110  if (deformation != null)
1111  {
1112  deformation.Type = typeName;
1113  }
1114  Deformations.Add(deformation, deformationElement);
1115  }
1116 #endif
1117  }
1118 
1119 #if CLIENT
1120  public Dictionary<SpriteDeformationParams, XElement> Deformations { get; private set; }
1121 
1122  public override bool Deserialize(ContentXElement element = null, bool recursive = true)
1123  {
1124  base.Deserialize(element, recursive);
1125  Deformations.ForEach(d => d.Key.SerializableProperties = SerializableProperty.DeserializeProperties(d.Key, d.Value));
1126  return SerializableProperties != null;
1127  }
1128 
1129  public override bool Serialize(ContentXElement element = null, bool recursive = true)
1130  {
1131  base.Serialize(element, recursive);
1132  Deformations.ForEach(d => SerializableProperty.SerializeProperties(d.Key, d.Value));
1133  return true;
1134  }
1135 
1136  public override void Reset()
1137  {
1138  base.Reset();
1139  Deformations.ForEach(d => d.Key.SerializableProperties = SerializableProperty.DeserializeProperties(d.Key, d.Value));
1140  }
1141 #endif
1142  }
1143 
1144  public class ColliderParams : SubParam
1145  {
1146  private string name;
1148  public override string Name
1149  {
1150  get
1151  {
1152  if (string.IsNullOrWhiteSpace(name))
1153  {
1154  name = GenerateName();
1155  }
1156  return name;
1157  }
1158  set
1159  {
1160  name = value;
1161  }
1162  }
1163 
1164  [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
1165  public float Radius { get; set; }
1166 
1167  [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
1168  public float Height { get; set; }
1169 
1170  [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 2048)]
1171  public float Width { get; set; }
1172 
1174  public BodyType BodyType { get; set; }
1175 
1176  public ColliderParams(ContentXElement element, RagdollParams ragdoll, string name = null) : base(element, ragdoll)
1177  {
1178  Name = name;
1179  }
1180  }
1181 
1183  {
1184  public class LightTexture : SubParam
1185  {
1186  public override string Name => "Light Texture";
1187 
1188  [Serialize("Content/Lights/pointlight_bright.png", IsPropertySaveable.Yes), Editable]
1189  public string Texture { get; private set; }
1190 
1191  [Serialize("0.5, 0.5", IsPropertySaveable.Yes), Editable(DecimalCount = 2)]
1192  public Vector2 Origin { get; set; }
1193 
1194  [Serialize("1.0, 1.0", IsPropertySaveable.Yes), Editable(DecimalCount = 2)]
1195  public Vector2 Size { get; set; }
1196 
1197  public LightTexture(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
1198  }
1199 
1200  public LightTexture Texture { get; private set; }
1201 
1202 #if CLIENT
1203  public Lights.LightSourceParams LightSource { get; private set; }
1204 #endif
1205 
1206  public LightSourceParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1207  {
1208 #if CLIENT
1209  LightSource = new Lights.LightSourceParams(element);
1210 #endif
1211  var lightTextureElement = element.GetChildElement("lighttexture");
1212  if (lightTextureElement != null)
1213  {
1214  Texture = new LightTexture(lightTextureElement, ragdoll);
1215  SubParams.Add(Texture);
1216  }
1217  }
1218 
1219 #if CLIENT
1220  public override bool Deserialize(ContentXElement element = null, bool recursive = true)
1221  {
1222  base.Deserialize(element, recursive);
1223  LightSource.Deserialize(element ?? Element);
1224  return SerializableProperties != null;
1225  }
1226 
1227  public override bool Serialize(ContentXElement element = null, bool recursive = true)
1228  {
1229  base.Serialize(element, recursive);
1230  LightSource.Serialize(element ?? Element);
1231  return true;
1232  }
1233 
1234  public override void Reset()
1235  {
1236  base.Reset();
1238  }
1239 #endif
1240  }
1241 
1242  // TODO: conditionals?
1243  public class AttackParams : SubParam
1244  {
1245  public Attack Attack { get; private set; }
1246 
1247  public AttackParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1248  {
1249  Attack = new Attack(element, ragdoll.SpeciesName.Value);
1250  }
1251 
1252  public override bool Deserialize(ContentXElement element = null, bool recursive = true)
1253  {
1254  base.Deserialize(element, recursive);
1255  Attack.Deserialize(element ?? Element, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
1256  return SerializableProperties != null;
1257  }
1258 
1259  public override bool Serialize(ContentXElement element = null, bool recursive = true)
1260  {
1261  base.Serialize(element, recursive);
1262  Attack.Serialize(element ?? Element);
1263  return true;
1264  }
1265 
1266  public override void Reset()
1267  {
1268  base.Reset();
1269  Attack.Deserialize(OriginalElement, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
1270  Attack.ReloadAfflictions(OriginalElement, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
1271  }
1272 
1273  public bool AddNewAffliction()
1274  {
1275  Serialize();
1276  var subElement = CreateElement("affliction",
1277  new XAttribute("identifier", "internaldamage"),
1278  new XAttribute("strength", 0f),
1279  new XAttribute("probability", 1.0f));
1280  Element.Add(subElement);
1281  Attack.ReloadAfflictions(Element, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
1282  Serialize();
1283  return true;
1284  }
1285 
1286  public bool RemoveAffliction(XElement affliction)
1287  {
1288  Serialize();
1289  affliction.Remove();
1290  Attack.ReloadAfflictions(Element, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
1291  return Serialize();
1292  }
1293  }
1294 
1296  {
1297  public DamageModifier DamageModifier { get; private set; }
1298 
1299  public DamageModifierParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll)
1300  {
1301  DamageModifier = new DamageModifier(element, ragdoll.SpeciesName.Value);
1302  }
1303 
1304  public override bool Deserialize(ContentXElement element = null, bool recursive = true)
1305  {
1306  base.Deserialize(element, recursive);
1307  DamageModifier.Deserialize(element ?? Element);
1308  return SerializableProperties != null;
1309  }
1310 
1311  public override bool Serialize(ContentXElement element = null, bool recursive = true)
1312  {
1313  base.Serialize(element, recursive);
1314  DamageModifier.Serialize(element ?? Element);
1315  return true;
1316  }
1317 
1318  public override void Reset()
1319  {
1320  base.Reset();
1322  }
1323  }
1324 
1325  public class SoundParams : SubParam
1326  {
1327  public override string Name => "Sound";
1328 
1330  public string Tag { get; private set; }
1331 
1332  public SoundParams(ContentXElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
1333  }
1334 
1335  public abstract class SubParam : ISerializableEntity
1336  {
1337  public virtual string Name { get; set; }
1338  public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
1339  public ContentXElement Element { get; set; }
1340  public ContentXElement OriginalElement { get; protected set; }
1341  public List<SubParam> SubParams { get; set; } = new List<SubParam>();
1342  public RagdollParams Ragdoll { get; private set; }
1343 
1344  public virtual string GenerateName() => Element.Name.ToString();
1345 
1346  protected ContentXElement CreateElement(string name, params object[] attrs)
1347  => new XElement(name, attrs).FromPackage(Element.ContentPackage);
1348 
1349  public SubParam(ContentXElement element, RagdollParams ragdoll)
1350  {
1351  Element = element;
1352  OriginalElement = new ContentXElement(element.ContentPackage, element);
1353  Ragdoll = ragdoll;
1355  }
1356 
1357  public virtual bool Deserialize(ContentXElement element = null, bool recursive = true)
1358  {
1359  element ??= Element;
1361  if (recursive)
1362  {
1363  SubParams.ForEach(sp => sp.Deserialize(recursive: true));
1364  }
1365  return SerializableProperties != null;
1366  }
1367 
1368  public virtual bool Serialize(ContentXElement element = null, bool recursive = true)
1369  {
1370  element ??= Element;
1371  SerializableProperty.SerializeProperties(this, element, true);
1372  if (recursive)
1373  {
1374  SubParams.ForEach(sp => sp.Serialize(recursive: true));
1375  }
1376  return true;
1377  }
1378 
1380  {
1382  SubParams.ForEach(sp => sp.SetCurrentElementAsOriginalElement());
1383  }
1384 
1385  public virtual void Reset()
1386  {
1387  // Don't use recursion, because the reset method might be overriden
1388  Deserialize(OriginalElement, recursive: false);
1389  SubParams.ForEach(sp => sp.Reset());
1390  }
1391 
1392 #if CLIENT
1394  public Dictionary<Affliction, SerializableEntityEditor> AfflictionEditors { get; private set; }
1395  public virtual void AddToEditor(ParamsEditor editor, bool recursive = true, int space = 0)
1396  {
1397  SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1398  if (this is DecorativeSpriteParams decSpriteParams)
1399  {
1400  new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, decSpriteParams.DecorativeSprite, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1401  }
1402  else if (this is DeformSpriteParams deformSpriteParams)
1403  {
1404  foreach (var deformation in deformSpriteParams.Deformation.Deformations.Keys)
1405  {
1406  new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, deformation, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1407  }
1408  }
1409  else if (this is AttackParams attackParams)
1410  {
1411  SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, attackParams.Attack, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1412  if (AfflictionEditors == null)
1413  {
1414  AfflictionEditors = new Dictionary<Affliction, SerializableEntityEditor>();
1415  }
1416  else
1417  {
1418  AfflictionEditors.Clear();
1419  }
1420  foreach (var affliction in attackParams.Attack.Afflictions.Keys)
1421  {
1422  var afflictionEditor = new SerializableEntityEditor(SerializableEntityEditor.RectTransform, affliction, inGame: false, showName: true);
1423  AfflictionEditors.Add(affliction, afflictionEditor);
1425  }
1426  }
1427  else if (this is LightSourceParams lightParams)
1428  {
1429  SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, lightParams.LightSource, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1430  }
1431  else if (this is DamageModifierParams damageModifierParams)
1432  {
1433  SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, damageModifierParams.DamageModifier, inGame: false, showName: true, titleFont: GUIStyle.LargeFont);
1434  }
1435  if (recursive)
1436  {
1437  SubParams.ForEach(sp => sp.AddToEditor(editor, true));
1438  }
1439  if (space > 0)
1440  {
1441  new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, space), editor.EditorBox.Content.RectTransform), style: null, color: new Color(20, 20, 20, 255))
1442  {
1443  CanBeFocused = false
1444  };
1445  }
1446  }
1447 #endif
1448  }
1449  #endregion
1450  }
1451 }
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:33
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