Client LuaCsForBarotrauma
CharacterParams.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using System.Xml.Linq;
5 using System.Xml;
6 using System.Linq;
8 using System.Collections.Immutable;
9 #if CLIENT
10 using SoundType = Barotrauma.CharacterSound.SoundType;
11 #endif
12 
13 namespace Barotrauma
14 {
19  {
21  public Identifier SpeciesName { get; private set; }
22 
24  public string Tags
25  {
26  get => tags.ConvertToString();
27  set => tags = value.ToIdentifiers().ToHashSet();
28  }
29  private HashSet<Identifier> tags = new HashSet<Identifier>();
30 
31  public bool HasTag(Identifier tag) => tags.Contains(tag);
32 
33  [Serialize("", IsPropertySaveable.Yes, description: "References to another species. Define only if the creature is a variant that needs to use a pre-existing translation."), Editable]
34  public Identifier SpeciesTranslationOverride { get; private set; }
35 
36  [Serialize("", IsPropertySaveable.Yes, description: "Overrides the name of the character, shown to the player. If the display name is not defined, the game first tries to find the translated name. If that is not found, the species name will be used."), Editable]
37  public string DisplayName { get; private set; }
38 
39  [Serialize("", IsPropertySaveable.Yes, description: "If defined, different species of the same group consider each other friendly and do not attack each other."), Editable]
40  public Identifier Group { get; private set; }
41 
42  [Serialize(false, IsPropertySaveable.Yes, description: "If enabled, the character is a humanoid and has different animation constraints relative to non-humanoid characters."), Editable(ReadOnly = true)]
43  public bool Humanoid { get; private set; }
44 
45  [Serialize(false, IsPropertySaveable.Yes, description: "If enabled, jobs can be assigned to characters of this species. Should be true for the player characters."), Editable(ReadOnly = true)]
46  public bool HasInfo { get; private set; }
47 
48  [Serialize(false, IsPropertySaveable.Yes, description: "Can the creature interact with items?"), Editable]
49  public bool CanInteract { get; private set; }
50 
51  [Serialize(true, IsPropertySaveable.Yes, description: "Can the creature use ladders? Doesn't have an effect, if CanInteract is false."), Editable]
52  public bool CanClimb { get; private set; }
53 
54  [Serialize(false, IsPropertySaveable.Yes, description: "If set true, this character only uses the climbing parameters defined in the walk parameters (not run)."), Editable]
55  public bool ForceSlowClimbing { get; private set; }
56 
57  [Serialize(false, IsPropertySaveable.Yes, description: "Should this character be treated as a husk?"), Editable]
58  public bool Husk { get; private set; }
59 
60  [Serialize("", IsPropertySaveable.Yes, description: "If this character can turn into a husk, which character it turns to? If not defined, uses the default pattern (e.g. Crawler -> Crawlerhusk, Human -> Humanhusk)."), Editable]
61  public Identifier HuskedSpecies { get; private set; }
62 
63  [Serialize("", IsPropertySaveable.Yes, description: "If this character is a husk, from what species it can be turned into? If not defined, uses the default pattern (e.g. Crawlerhusk -> Crawler, Humanhusk -> Human)."), Editable]
64  public Identifier NonHuskedSpecies { get; private set; }
65 
66  [Serialize(false, IsPropertySaveable.Yes, description:"Should this character use a special husk appendage, attached to the ragdoll, when it turns into a husk?"), Editable]
67  public bool UseHuskAppendage { get; private set; }
68 
69  [Serialize(false, IsPropertySaveable.Yes, description: "Does this character need oxygen to survive? Enabling this also makes the character vulnerable to high pressure when swimming outside of the submarine."), Editable]
70  public bool NeedsAir { get; set; }
71 
72  [Serialize(false, IsPropertySaveable.Yes, description: "Can the creature live without water or does it die on dry land?"), Editable]
73  public bool NeedsWater { get; set; }
74 
75  [Serialize(false, IsPropertySaveable.Yes, description: "Note: non-humans with a human AI aren't fully supported. Enabling this on a non-human character may lead to issues.")]
76  public bool UseHumanAI { get; set; }
77 
78  [Serialize(false, IsPropertySaveable.Yes, description: "Is this creature an artificial creature, like robot or machine that shouldn't be affected by afflictions that affect only organic creatures? Overrides DoesBleed."), Editable]
79  public bool IsMachine { get; set; }
80 
81  [Serialize(false, IsPropertySaveable.No, description:"Is the character able to send messages in the chat?"), Editable]
82  public bool CanSpeak { get; set; }
83 
84  [Serialize(true, IsPropertySaveable.Yes, description:"Is there a health bar shown above the character when it takes damage? Defaults to true."), Editable]
85  public bool ShowHealthBar { get; private set; }
86 
87  [Serialize(false, IsPropertySaveable.Yes, description: "Is this character's health shown at the top of the player's screen when they are in an active encounter?"), Editable]
88  public bool UseBossHealthBar { get; private set; }
89 
90  [Serialize(100f, IsPropertySaveable.Yes, description: "How much noise the character makes when moving?"), Editable(minValue: 0f, maxValue: 100000f)]
91  public float Noise { get; set; }
92 
93  [Serialize(100f, IsPropertySaveable.Yes, description: "How visible the character is?"), Editable(minValue: 0f, maxValue: 100000f)]
94  public float Visibility { get; set; }
95 
96  [Serialize("blood", IsPropertySaveable.Yes), Editable]
97  public string BloodDecal { get; private set; }
98 
99  [Serialize("blooddrop", IsPropertySaveable.Yes), Editable]
100  public string BleedParticleAir { get; private set; }
101 
102  [Serialize("waterblood", IsPropertySaveable.Yes), Editable]
103  public string BleedParticleWater { get; private set; }
104 
105  [Serialize(1f, IsPropertySaveable.Yes, description: "A multiplier to increase or decrease the number of bleeding particles to create."), Editable]
106  public float BleedParticleMultiplier { get; private set; }
107 
108  [Serialize(true, IsPropertySaveable.Yes, description: "Can the creature eat bodies? Used by player controlled creatures to allow them to eat. Currently applicable only to non-humanoids. To allow an AI controller to eat, just add an ai target with the state \"eat\""), Editable]
109  public bool CanEat { get; set; }
110 
111  [Serialize(10f, IsPropertySaveable.Yes, description: "How effectively/easily the character eats other characters. Affects the forces, the amount of particles, and the time required before the target is eaten away"), Editable(MinValueFloat = 1, MaxValueFloat = 1000, ValueStep = 1)]
112  public float EatingSpeed { get; set; }
113 
114  [Serialize(true, IsPropertySaveable.Yes, description: "Should the character AI use waypoints defined in the level to find a path to its targets?"), Editable]
115  public bool UsePathFinding { get; set; }
116 
117  [Serialize(1f, IsPropertySaveable.Yes, "Decreases the intensive path finding call frequency. Set to a lower value for insignificant creatures to improve performance."), Editable(minValue: 0f, maxValue: 1f)]
118  public float PathFinderPriority { get; set; }
119 
120  [Serialize(false, IsPropertySaveable.Yes, description: "Should the character be hidden in the sonar?"), Editable]
121  public bool HideInSonar { get; set; }
122 
123  [Serialize(false, IsPropertySaveable.Yes, description: "Should the character be hidden when using thermal goggles?"), Editable]
124  public bool HideInThermalGoggles { get; set; }
125 
126  [Serialize(0f, IsPropertySaveable.Yes, description: "If set to a value greater than zero, this character creates disrupting noise on the sonar when within range."), Editable]
127  public float SonarDisruption { get; set; }
128 
129  [Serialize(0f, IsPropertySaveable.Yes, description: "Range at which \"long distance\" blips for this character will appear on the sonar (used on some of the Abyss monsters)."), Editable]
130  public float DistantSonarRange { get; set; }
131 
132  [Serialize(25000f, IsPropertySaveable.Yes, "If the character is farther than this (in pixels) from the sub and the players, it will be disabled. The halved value is used for triggering simple physics where the ragdoll is disabled and only the main collider is updated."), Editable(MinValueFloat = 10000f, MaxValueFloat = 100000f)]
133  public float DisableDistance { get; set; }
134 
135  [Serialize(10f, IsPropertySaveable.Yes, "How frequent the recurring idle and attack sounds are?"), Editable(MinValueFloat = 1f, MaxValueFloat = 100f)]
136  public float SoundInterval { get; set; }
137 
138  [Serialize(false, IsPropertySaveable.Yes, description: "Should the character be drawn on top of characters that do not have this set? This currently has no effect if the character has no deformable sprites."), Editable]
139  public bool DrawLast { get; set; }
140 
141  [Serialize(1.0f, IsPropertySaveable.Yes, "Tells the bots how much they should prefer targeting this character with submarine weapons. Defaults to 1. Set 0 to tell the bots not to target this character at all. Distance to the target affects the decision making."), Editable]
142  public float AITurretPriority { get; set; }
143 
144  [Serialize(1.0f, IsPropertySaveable.Yes, "Tells the bots how much they should prefer targeting this character with submarine weapons tagged as \"slowturret\", like railguns. The tag is arbitrary and can be added to any turrets, just like the priority. Defaults to 1. Not used if AITurretPriority is 0. Distance to the target affects the decision making."), Editable]
145  public float AISlowTurretPriority { get; set; }
146 
147  [Serialize("", IsPropertySaveable.Yes, description: "Identifier or tag of the item the character's items are placed inside when the character despawns."), Editable]
148  public Identifier DespawnContainer { get; private set; }
149 
150  public readonly CharacterFile File;
151 
152  public XDocument VariantFile { get; private set; }
153 
154  public readonly List<SubParam> SubParams = new List<SubParam>();
155  public readonly List<SoundParams> Sounds = new List<SoundParams>();
156  public readonly List<ParticleParams> BloodEmitters = new List<ParticleParams>();
157  public readonly List<ParticleParams> GibEmitters = new List<ParticleParams>();
158  public readonly List<ParticleParams> DamageEmitters = new List<ParticleParams>();
159  public readonly List<InventoryParams> Inventories = new List<InventoryParams>();
160  public HealthParams Health { get; private set; }
167  public AIParams AI { get; private set; }
168 
170  {
171  File = file;
172  Load();
173  }
174 
175  protected override string GetName() => "Character Config File";
176 
177  public override ContentXElement MainElement
178  {
179  get
180  {
181  if (base.MainElement == null) { return null; }
182  return base.MainElement.IsOverride() ? base.MainElement.FirstElement() : base.MainElement;
183  }
184  }
185 
186  public static XElement CreateVariantXml(ContentXElement variantXML, ContentXElement baseXML)
187  {
188  XElement newXml = variantXML.CreateVariantXML(baseXML);
189  XElement variantAi = variantXML.GetChildElement("ai");
190  XElement baseAi = baseXML.GetChildElement("ai");
191  if (baseAi is null || baseAi.Elements().None()
192  || variantAi is null || variantAi.Elements().None())
193  {
194  return newXml;
195  }
196  // CreateVariantXML seems to merge the ai targets so that in the new xml we have both the old and the new target definitions.
197  var finalAiElement = newXml.GetChildElement("ai");
198  var processedTags = new HashSet<string>();
199  foreach (var aiTarget in finalAiElement.Elements().ToArray())
200  {
201  string tag = aiTarget.GetAttributeString("tag", null);
202  if (tag == null) { continue; }
203  if (processedTags.Contains(tag))
204  {
205  aiTarget.Remove();
206  continue;
207  }
208  processedTags.Add(tag);
209  var matchInSelf = variantAi.Elements().FirstOrDefault(e => e.GetAttributeString("tag", null) == tag);
210  var matchInParent = baseAi.Elements().FirstOrDefault(e => e.GetAttributeString("tag", null) == tag);
211  if (matchInSelf != null && matchInParent != null)
212  {
213  aiTarget.ReplaceWith(new XElement(matchInSelf));
214  }
215  }
216  return newXml;
217  }
218 
219  public bool Load()
220  {
221  UpdatePath(File.Path);
222  doc = XMLExtensions.TryLoadXml(Path);
223  if (MainElement == null)
224  {
225  DebugConsole.ThrowError("Main element null! Failed to load character params.");
226  return false;
227  }
228  Identifier variantOf = MainElement.VariantOf();
229  if (!variantOf.IsEmpty)
230  {
231  VariantFile = new XDocument(doc);
232  #warning TODO: determine that CreateVariantXML is equipped to do this
234  var oldElement = MainElement;
235  var parentElement = (XContainer)oldElement.Parent ?? doc; oldElement.Remove(); parentElement.Add(newRoot);
236  }
238  OriginalElement = new XElement(MainElement).FromPackage(Path.ContentPackage);
239  if (SpeciesName.IsEmpty && MainElement != null)
240  {
241  //backwards compatibility
243  }
244  CreateSubParams();
245  return IsLoaded;
246  }
247 
248  public bool Save(string fileNameWithoutExtension = null)
249  {
250  // Disable saving variants for now. Making it work probably requires more work.
251  if (VariantFile != null) { return false; }
252  Serialize();
253  return base.Save(fileNameWithoutExtension, new XmlWriterSettings
254  {
255  Indent = true,
256  OmitXmlDeclaration = true,
257  NewLineOnAttributes = false
258  });
259  }
260 
261  public override bool Reset(bool forceReload = false)
262  {
263  if (forceReload)
264  {
265  return Load();
266  }
267  Deserialize(OriginalElement, alsoChildren: true);
268  SubParams.ForEach(sp => sp.Reset());
269  return true;
270  }
271 
272  public static bool CompareGroup(Identifier group1, Identifier group2) => group1 != Identifier.Empty && group2 != Identifier.Empty && group1 == group2;
273 
274  protected void CreateSubParams()
275  {
276  if (MainElement == null)
277  {
278  DebugConsole.ThrowError("Main element null, cannot create sub params!");
279  return;
280  }
281  SubParams.Clear();
282  var healthElement = MainElement.GetChildElement("health");
283  if (healthElement != null)
284  {
285  Health = new HealthParams(healthElement, this);
286  }
287  else
288  {
289  DebugConsole.ThrowError($"No health parameters defined for character \"{(SpeciesName)}\".");
290  Health = new HealthParams(null, this);
291  }
292  SubParams.Add(Health);
293  // TODO: support for multiple ai elements?
294  var ai = MainElement.GetChildElement("ai");
295  if (ai != null)
296  {
297  AI = new AIParams(ai, this);
298  SubParams.Add(AI);
299  }
300  foreach (var element in MainElement.GetChildElements("bloodemitter"))
301  {
302  var emitter = new ParticleParams(element, this);
303  BloodEmitters.Add(emitter);
304  SubParams.Add(emitter);
305  }
306  foreach (var element in MainElement.GetChildElements("gibemitter"))
307  {
308  var emitter = new ParticleParams(element, this);
309  GibEmitters.Add(emitter);
310  SubParams.Add(emitter);
311  }
312  foreach (var element in MainElement.GetChildElements("damageemitter"))
313  {
314  var emitter = new ParticleParams(element, this);
315  GibEmitters.Add(emitter);
316  SubParams.Add(emitter);
317  }
318  foreach (var soundElement in MainElement.GetChildElements("sound"))
319  {
320  var sound = new SoundParams(soundElement, this);
321  Sounds.Add(sound);
322  SubParams.Add(sound);
323  }
324  foreach (var inventoryElement in MainElement.GetChildElements("inventory"))
325  {
326  var inventory = new InventoryParams(inventoryElement, this);
327  Inventories.Add(inventory);
328  SubParams.Add(inventory);
329  }
330  }
331 
332  public bool Deserialize(XElement element = null, bool alsoChildren = true, bool recursive = true, bool loadDefaultValues = true)
333  {
334  if (base.Deserialize(element))
335  {
336  //backwards compatibility
337  if (SpeciesName.IsEmpty)
338  {
339  SpeciesName = element.GetAttributeIdentifier("name", "[NAME NOT GIVEN]");
340  }
341  if (alsoChildren)
342  {
343  SubParams.ForEach(p => p.Deserialize(recursive));
344  }
345  return true;
346  }
347  return false;
348  }
349 
350  public bool Serialize(XElement element = null, bool alsoChildren = true, bool recursive = true)
351  {
352  if (base.Serialize(element))
353  {
354  if (alsoChildren)
355  {
356  SubParams.ForEach(p => p.Serialize(recursive));
357  }
358  return true;
359  }
360  return false;
361  }
362 
363 #if CLIENT
364  public void AddToEditor(ParamsEditor editor, bool alsoChildren = true, bool recursive = true, int space = 0)
365  {
366  base.AddToEditor(editor);
367  if (alsoChildren)
368  {
369  SubParams.ForEach(s => s.AddToEditor(editor, recursive));
370  }
371  if (space > 0)
372  {
373  new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, (int)(space * GUI.yScale)), editor.EditorBox.Content.RectTransform), style: null, color: ParamsEditor.Color)
374  {
375  CanBeFocused = false
376  };
377  }
378  }
379 #endif
380 
381  public bool AddSound() => TryAddSubParam(CreateElement("sound"), (e, c) => new SoundParams(e, c), out _, Sounds);
382 
383  public void AddInventory() => TryAddSubParam(CreateElement("inventory", new XElement("item")), (e, c) => new InventoryParams(e, c), out _, Inventories);
384 
385  public void AddBloodEmitter() => AddEmitter("bloodemitter");
386  public void AddGibEmitter() => AddEmitter("gibemitter");
387  public void AddDamageEmitter() => AddEmitter("damageemitter");
388 
389  private void AddEmitter(string type)
390  {
391  switch (type)
392  {
393  case "gibemitter":
394  TryAddSubParam(CreateElement(type), (e, c) => new ParticleParams(e, c), out _, GibEmitters);
395  break;
396  case "bloodemitter":
397  TryAddSubParam(CreateElement(type), (e, c) => new ParticleParams(e, c), out _, BloodEmitters);
398  break;
399  case "damageemitter":
400  TryAddSubParam(CreateElement(type), (e, c) => new ParticleParams(e, c), out _, DamageEmitters);
401  break;
402  default: throw new NotImplementedException(type);
403  }
404  }
405 
406  public bool RemoveSound(SoundParams soundParams) => RemoveSubParam(soundParams);
407  public bool RemoveBloodEmitter(ParticleParams emitter) => RemoveSubParam(emitter, BloodEmitters);
408  public bool RemoveGibEmitter(ParticleParams emitter) => RemoveSubParam(emitter, GibEmitters);
409  public bool RemoveDamageEmitter(ParticleParams emitter) => RemoveSubParam(emitter, DamageEmitters);
410  public bool RemoveInventory(InventoryParams inventory) => RemoveSubParam(inventory, Inventories);
411 
412  protected bool RemoveSubParam<T>(T subParam, IList<T> collection = null) where T : SubParam
413  {
414  if (subParam == null || subParam.Element == null || subParam.Element.Parent == null) { return false; }
415  if (collection != null && !collection.Contains(subParam)) { return false; }
416  if (!SubParams.Contains(subParam)) { return false; }
417  collection?.Remove(subParam);
418  SubParams.Remove(subParam);
419  subParam.Element.Remove();
420  return true;
421  }
422 
423  protected bool TryAddSubParam<T>(ContentXElement element, Func<ContentXElement, CharacterParams, T> constructor, out T subParam, IList<T> collection = null, Func<IList<T>, bool> filter = null) where T : SubParam
424  {
425  subParam = constructor(element, this);
426  if (collection != null && filter != null)
427  {
428  if (filter(collection)) { return false; }
429  }
430  MainElement.Add(element);
431  SubParams.Add(subParam);
432  collection?.Add(subParam);
433  return subParam != null;
434  }
435 
436  #region Subparams
437  public class SoundParams : SubParam
438  {
439  public override string Name => "Sound";
440 
442  public string File { get; private set; }
443 
444 #if CLIENT
445  [Serialize(SoundType.Idle, IsPropertySaveable.Yes), Editable]
446  public SoundType State { get; private set; }
447 #endif
448 
449  [Serialize(1000f, IsPropertySaveable.Yes), Editable(minValue: 0f, maxValue: 10000f)]
450  public float Range { get; private set; }
451 
452  [Serialize(1.0f, IsPropertySaveable.Yes), Editable(minValue: 0f, maxValue: 2.0f)]
453  public float Volume { get; private set; }
454 
455  [Serialize("", IsPropertySaveable.Yes, description: "Which tags are required for this sound to play?"), Editable()]
456  public string Tags
457  {
458  get => TagSet.ConvertToString();
459  private set => TagSet = value.ToIdentifiers().ToImmutableHashSet();
460  }
461 
462  public ImmutableHashSet<Identifier> TagSet { get; private set; } = ImmutableHashSet<Identifier>.Empty;
463 
464  public SoundParams(ContentXElement element, CharacterParams character) : base(element, character)
465  {
466  Identifier genderFallback = element.GetAttributeIdentifier("gender", "");
467  if (genderFallback != Identifier.Empty && genderFallback != "None")
468  {
469  TagSet = TagSet.Add(genderFallback);
470  }
471  }
472  }
473 
474  public class ParticleParams : SubParam
475  {
476  private string name;
477  public override string Name
478  {
479  get
480  {
481  if (name == null && Element != null)
482  {
483  name = Element.Name.ToString().FormatCamelCaseWithSpaces();
484  }
485  return name;
486  }
487  }
488 
490  public string Particle { get; set; }
491 
492  [Serialize(0f, IsPropertySaveable.Yes), Editable(-360f, 360f, decimals: 0)]
493  public float AngleMin { get; private set; }
494 
495  [Serialize(0f, IsPropertySaveable.Yes), Editable(-360f, 360f, decimals: 0)]
496  public float AngleMax { get; private set; }
497 
498  [Serialize(1.0f, IsPropertySaveable.Yes), Editable(0f, 100f, decimals: 2)]
499  public float ScaleMin { get; private set; }
500 
501  [Serialize(1.0f, IsPropertySaveable.Yes), Editable(0f, 100f, decimals: 2)]
502  public float ScaleMax { get; private set; }
503 
504  [Serialize(0f, IsPropertySaveable.Yes), Editable(0f, 10000f, decimals: 0)]
505  public float VelocityMin { get; private set; }
506 
507  [Serialize(0f, IsPropertySaveable.Yes), Editable(0f, 10000f, decimals: 0)]
508  public float VelocityMax { get; private set; }
509 
510  [Serialize(0f, IsPropertySaveable.Yes), Editable(0f, 100f, decimals: 2)]
511  public float EmitInterval { get; private set; }
512 
513  [Serialize(0, IsPropertySaveable.Yes), Editable(0, 1000)]
514  public int ParticlesPerSecond { get; private set; }
515 
516  [Serialize(0, IsPropertySaveable.Yes), Editable(0, 1000)]
517  public int ParticleAmount { get; private set; }
518 
519  [Serialize(false, IsPropertySaveable.Yes), Editable]
520  public bool HighQualityCollisionDetection { get; private set; }
521 
522  [Serialize(false, IsPropertySaveable.Yes), Editable]
523  public bool CopyEntityAngle { get; private set; }
524 
525  public ParticleParams(ContentXElement element, CharacterParams character) : base(element, character) { }
526  }
527 
528  public class HealthParams : SubParam
529  {
530  public override string Name => "Health";
531 
532  [Serialize(100f, IsPropertySaveable.Yes, description: "How much (max) health does the character have?"), Editable(minValue: 1, maxValue: 10000f)]
533  public float Vitality { get; set; }
534 
536  public bool DoesBleed { get; set; }
537 
538  [Serialize(float.PositiveInfinity, IsPropertySaveable.Yes), Editable(minValue: 0, maxValue: float.PositiveInfinity)]
539  public float CrushDepth { get; set; }
540 
541  // Make editable?
542  [Serialize(false, IsPropertySaveable.Yes)]
543  public bool UseHealthWindow { get; set; }
544 
545  [Serialize(0f, IsPropertySaveable.Yes, description: "How easily the character heals from the bleeding wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 100, DecimalCount = 2)]
546  public float BleedingReduction { get; set; }
547 
548  [Serialize(0f, IsPropertySaveable.Yes, description: "How easily the character heals from the burn wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 100, DecimalCount = 2)]
549  public float BurnReduction { get; set; }
550 
551  [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
552  public float ConstantHealthRegeneration { get; set; }
553 
554  [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 100, DecimalCount = 2)]
555  public float HealthRegenerationWhenEating { get; set; }
556 
557  [Serialize(false, IsPropertySaveable.Yes), Editable]
558  public bool StunImmunity { get; set; }
559 
560  [Serialize(false, IsPropertySaveable.Yes), Editable]
561  public bool PoisonImmunity { get; set; }
562 
563  [Serialize(1f, IsPropertySaveable.Yes, description: "1 = default, 0 = immune."), Editable(MinValueFloat = 0f, MaxValueFloat = 1000, DecimalCount = 1)]
564  public float PoisonVulnerability { get; set; }
565 
567  public float EmpVulnerability { get; set; }
568 
569  [Serialize(true, IsPropertySaveable.Yes, description: "Apply movement penalties when legs or tail limbs get damaged. Enabled by default."), Editable]
570  public bool ApplyMovementPenalties { get; set; }
571 
572  [Serialize(true, IsPropertySaveable.Yes, description: "Normally characters die when they don't have a head. But maybe not all of them?"), Editable]
573  public bool DieFromBeheading { get; set; }
574 
575  [Serialize(false, IsPropertySaveable.Yes, description: "Severing legs doesn't work with most characters, because we'd need to take that into account with the walking animations and the standing position of the main collider etc. But there might be cases where you'll want to override this default."), Editable]
576  public bool AllowSeveringLegs { get; set; }
577 
578  [Serialize(false, IsPropertySaveable.Yes, description: "Can afflictions affect the face/body tint of the character."), Editable]
579  public bool ApplyAfflictionColors { get; private set; }
580 
581  [Serialize("", IsPropertySaveable.Yes, description:"A comma-separated list of identifiers of afflictions that the creature is immune to."), Editable]
582  public string Immunities { get; private set; }
583 
584  private ImmutableHashSet<Identifier> _immunityIdentifiers;
585  public IEnumerable<Identifier> ImmunityIdentifiers
586  {
587  get
588  {
589  _immunityIdentifiers ??= Element.GetAttributeIdentifierArray("immunities", Array.Empty<Identifier>()).ToImmutableHashSet();
590  return _immunityIdentifiers;
591  }
592  }
593 
594  // TODO: limbhealths, sprite?
595 
596  public HealthParams(ContentXElement element, CharacterParams character) : base(element, character)
597  {
598  //backwards compatibility
599  if (CrushDepth < 0)
600  {
601  //invert y, convert to meters, and add 1000 to be on the safe side (previously the value would be from the bottom of the level)
602  float newCrushDepth = -CrushDepth * Physics.DisplayToRealWorldRatio + 1000;
603  DebugConsole.AddWarning($"Character \"{character.SpeciesName}\" has a negative crush depth. "+
604  "Previously the crush depths were defined as display units (e.g. -30000 would correspond to 300 meters below the level), "+
605  "but now they're in meters (e.g. 3000 would correspond to a depth of 3000 meters displayed on the nav terminal). "+
606  $"Changing the crush depth from {CrushDepth} to {newCrushDepth}.",
607  element.ContentPackage);
608  CrushDepth = newCrushDepth;
609  }
610  }
611  }
612 
613  public class InventoryParams : SubParam
614  {
615  public class InventoryItem : SubParam
616  {
617  public override string Name => "Item";
618 
619  [Serialize("", IsPropertySaveable.Yes, description: "Item identifier."), Editable()]
620  public string Identifier { get; private set; }
621 
622  public InventoryItem(ContentXElement element, CharacterParams character) : base(element, character) { }
623  }
624 
625  public override string Name => "Inventory";
626 
627  [Serialize("Any, Any", IsPropertySaveable.Yes, description: "Which slots the inventory holds? Accepted types: None, Any, RightHand, LeftHand, Head, InnerClothes, OuterClothes, Headset, and Card."), Editable()]
628  public string Slots { get; private set; }
629 
630  [Serialize(false, IsPropertySaveable.Yes), Editable]
631  public bool AccessibleWhenAlive { get; private set; }
632 
633  [Serialize(1.0f, IsPropertySaveable.Yes, description: "What are the odds that this inventory is spawned on the character?"), Editable(minValue: 0f, maxValue: 1.0f)]
634  public float Commonness { get; private set; }
635 
636  public List<InventoryItem> Items { get; private set; } = new List<InventoryItem>();
637 
638  public InventoryParams(ContentXElement element, CharacterParams character) : base(element, character)
639  {
640  foreach (var itemElement in element.GetChildElements("item"))
641  {
642  var item = new InventoryItem(itemElement, character);
643  SubParams.Add(item);
644  Items.Add(item);
645  }
646  }
647 
648  public void AddItem(string identifier = null)
649  {
650  if (Element == null) { return; }
651  identifier ??= "";
652  var element = CreateElement("item", new XAttribute("identifier", identifier));
653  Element.Add(element);
654  var item = new InventoryItem(element, Character);
655  SubParams.Add(item);
656  Items.Add(item);
657  }
658 
659  public bool RemoveItem(InventoryItem item) => RemoveSubParam(item, Items);
660  }
661 
662  public class AIParams : SubParam
663  {
664  public override string Name => "AI";
665 
666  [Serialize(1.0f, IsPropertySaveable.Yes, description: "How strong other characters think this character is? Only affects AI."), Editable()]
667  public float CombatStrength { get; private set; }
668 
669  [Serialize(1.0f, IsPropertySaveable.Yes, description: "Affects how far the character can see the targets. Used as a multiplier."), Editable(minValue: 0f, maxValue: 10f)]
670  public float Sight { get; private set; }
671 
672  [Serialize(1.0f, IsPropertySaveable.Yes, description: "Affects how far the character can hear the targets. Used as a multiplier."), Editable(minValue: 0f, maxValue: 10f)]
673  public float Hearing { get; private set; }
674 
675  [Serialize(-1.0f, IsPropertySaveable.Yes, description: "Hard limit to how far the character can spot targets from, regardless of the sight/hearing or how visible or how much noise the target is making. Not used if set to negative."), Editable]
676  public float MaxPerceptionDistance { get; set; }
677 
678  [Serialize(100f, IsPropertySaveable.Yes, description: "How much the targeting priority increases each time the character takes damage. Works like the greed value, described above. The default value is 100."), Editable(minValue: -1000f, maxValue: 1000f)]
679  public float AggressionHurt { get; private set; }
680 
681  [Serialize(10f, IsPropertySaveable.Yes, description: "How much the targeting priority increases each time the character does damage to the target. The actual priority adjustment is calculated based on the damage percentage multiplied by the greed value. The default value is 10, which means the priority will increase by 1 every time the character does damage 10% of the target's current health. If the damage is 50%, then the priority increase is 5."), Editable(minValue: 0f, maxValue: 1000f)]
682  public float AggressionGreed { get; private set; }
683 
684  [Serialize(0f, IsPropertySaveable.Yes, description: "If the health drops below this threshold, the character flees. In percentages."), Editable(minValue: 0f, maxValue: 100f)]
685  public float FleeHealthThreshold { get; set; }
686 
687  [Serialize(false, IsPropertySaveable.Yes, description: "Does the character attack when provoked? When enabled, overrides the predefined targeting state with Attack and increases the priority of it."), Editable()]
688  public bool AttackWhenProvoked { get; private set; }
689 
690  [Serialize(false, IsPropertySaveable.Yes, description: "The character will flee for a brief moment when being shot at if not performing an attack."), Editable]
691  public bool AvoidGunfire { get; private set; }
692 
693  [Serialize(0f, IsPropertySaveable.Yes, description: "How much damage is required for single attack to trigger avoiding/releasing targets."), Editable(minValue: 0f, maxValue: 1000f)]
694  public float DamageThreshold { get; private set; }
695 
696  [Serialize(3f, IsPropertySaveable.Yes, description: "How long the creature avoids gunfire. Also used when the creature is unlatched."), Editable(minValue: 0f, maxValue: 100f)]
697  public float AvoidTime { get; private set; }
698 
699  [Serialize(20f, IsPropertySaveable.Yes, description: "How long the creature flees before returning to normal state. When the creature sees the target or is being chased, it will always flee, if it's in the flee state."), Editable(minValue: 0f, maxValue: 100f)]
700  public float MinFleeTime { get; private set; }
701 
702  [Serialize(false, IsPropertySaveable.Yes, description: "Does the character try to break inside the sub?"), Editable]
703  public bool AggressiveBoarding { get; private set; }
704 
705  [Serialize(true, IsPropertySaveable.Yes, description: "Enforce aggressive behavior if the creature is spawned as a target of a monster mission."), Editable]
706  public bool EnforceAggressiveBehaviorForMissions { get; private set; }
707 
708  [Serialize(true, IsPropertySaveable.Yes, description: "Should the character target or ignore walls when it's outside the submarine."), Editable]
709  public bool TargetOuterWalls { get; private set; }
710 
711  [Serialize(false, IsPropertySaveable.Yes, description: "If enabled, the character chooses randomly from the available attacks. The priority is used as a weight for weighted random."), Editable]
712  public bool RandomAttack { get; private set; }
713 
714  [Serialize(false, IsPropertySaveable.Yes, description:"Does the creature know how to open doors (still requires a proper ID card). Humans can always open doors (They don't use this AI definition)."), Editable]
715  public bool CanOpenDoors { get; private set; }
716 
717  [Serialize(false, IsPropertySaveable.Yes, description:"Unlike human AI, monsters normally only use pathfinding when they are inside the submarine. When this is enabled, the monsters can also use pathfinding to get inside the sub. In practice, via doors and hatches."), Editable]
718  public bool UsePathFindingToGetInside { get; set; }
719 
720  [Serialize(false, IsPropertySaveable.Yes, description: "Does the creature close the doors behind it. Humans don't use this AI definition."), Editable]
721  public bool KeepDoorsClosed { get; private set; }
722 
723  [Serialize(true, IsPropertySaveable.Yes, "Is the creature allowed to navigate from and into the depths of the abyss? When enabled, the creatures will try to avoid the depths."), Editable]
724  public bool AvoidAbyss { get; set; }
725 
726  [Serialize(false, IsPropertySaveable.Yes, "Does the creature try to keep in the abyss? Has effect only when AvoidAbyss is false."), Editable]
727  public bool StayInAbyss { get; set; }
728 
729  [Serialize(false, IsPropertySaveable.Yes, "Does the creature patrol the flooded hulls while idling inside a friendly submarine?"), Editable]
730  public bool PatrolFlooded { get; set; }
731 
732  [Serialize(false, IsPropertySaveable.Yes, "Does the creature patrol the dry hulls while idling inside a friendly submarine?"), Editable]
733  public bool PatrolDry { get; set; }
734 
735  [Serialize(0f, IsPropertySaveable.Yes, description: "Initial aggression used in the circle attack pattern (0-100). The aggression affects how close and how fast to the target the monster circles."), Editable]
736  public float StartAggression { get; private set; }
737 
738  [Serialize(100f, IsPropertySaveable.Yes, description: "Maximum aggression used in the circle attack pattern (0-100). The aggression affects how close and how fast to the target the monster circles."), Editable]
739  public float MaxAggression { get; private set; }
740 
741  [Serialize(0f, IsPropertySaveable.Yes, description: "How quickly the aggression level increases from StartAggression to MaxAggression when using the circle attack pattern. Artificial amount, applied once per attack cycle."), Editable]
742 
743  public float AggressionCumulation { get; private set; }
744 
745  [Serialize(WallTargetingMethod.Target, IsPropertySaveable.Yes, description: "Defines the method of checking whether there's a blocking (submarine) wall."), Editable]
746  public WallTargetingMethod WallTargetingMethod { get; private set; }
747 
748  [Serialize(0f, IsPropertySaveable.Yes, "How likely it is that the creature plays dead (= ragdolls) while idling? Only allowed inside a sub (not in the open waters). Evaluated once, when the creature spawns."), Editable]
749  public float PlayDeadProbability { get; set; }
750 
751  public IEnumerable<TargetParams> Targets => targets;
752  private readonly List<TargetParams> targets = new List<TargetParams>();
753 
754  public AIParams(ContentXElement element, CharacterParams character) : base(element, character)
755  {
756  if (element == null) { return; }
757  element.GetChildElements("target").ForEach(t => AddTarget(t));
758  element.GetChildElements("targetpriority").ForEach(t => AddTarget(t));
759  }
760 
764  private bool TryAddTarget(ContentXElement targetElement, out TargetParams target)
765  {
766  string tag = targetElement.GetAttributeString("tag", null);
767  if (HasTag(tag))
768  {
769  target = null;
770  DebugConsole.AddWarning($"Trying to add multiple targets with the same tag ('{tag}') defined! Only the first will be used!", targetElement.ContentPackage);
771  }
772  else
773  {
774  target = AddTarget(targetElement);
775  }
776  return target != null;
777  }
778 
782  private TargetParams AddTarget(ContentXElement targetElement)
783  {
784  var target = new TargetParams(targetElement, Character);
785  targets.Add(target);
786  SubParams.Add(target);
787  return target;
788  }
789 
790  public bool TryAddEmptyTarget(out TargetParams targetParams) => TryAddNewTarget("newtarget" + targets.Count, AIState.Attack, 0f, out targetParams);
791 
792  public bool TryAddNewTarget(string tag, AIState state, float priority, out TargetParams targetParams) =>
793  TryAddNewTarget(tag.ToIdentifier(), state, priority, out targetParams);
794 
795  public bool TryAddNewTarget(Identifier tag, AIState state, float priority, out TargetParams targetParams)
796  {
797  if (Element == null)
798  {
799  targetParams = null;
800  return false;
801  }
802  var element = TargetParams.CreateNewElement(Character, tag, state, priority);
803  if (TryAddTarget(element, out targetParams))
804  {
805  Element.Add(element);
806  return true;
807  }
808  else
809  {
810  return false;
811  }
812  }
813 
814  public bool HasTag(string tag) => HasTag(tag.ToIdentifier());
815 
816  public bool HasTag(Identifier tag)
817  {
818  if (tag == null) { return false; }
819  return targets.Any(t => t.Tag == tag);
820  }
821 
822  public bool RemoveTarget(TargetParams target) => RemoveSubParam(target, targets);
823 
824  public IEnumerable<TargetParams> GetMatchingTargets(Func<TargetParams, bool> predicate) => targets.Where(predicate);
825  public IEnumerable<TargetParams> GetTargets(Identifier target) => GetMatchingTargets(t => t.Tag == target);
826  public IEnumerable<TargetParams> GetTargets(Character target) => GetMatchingTargets(t => t.Tag == target.SpeciesName || t.Tag == target.Params.Group || target.Params.HasTag(t.Tag));
829 
830  private static TargetParams GetHighestPriorityTarget(IEnumerable<TargetParams> targetParams) => targetParams.MaxBy(static t => t.Priority);
831 
832  public bool TryGetTargets(Identifier target, out IEnumerable<TargetParams> targetParams)
833  {
834  targetParams = GetTargets(target);
835  return targetParams.Any();
836  }
837 
838  public bool TryGetTargets(Character target, out IEnumerable<TargetParams> targetParams)
839  {
840  targetParams = GetTargets(target);
841  return targetParams.Any();
842  }
843 
844  public bool TryGetHighestPriorityTarget(Identifier target, out TargetParams targetParams)
845  {
846  targetParams = GetHighestPriorityTarget(target);
847  return targetParams != null;
848  }
849 
850  public bool TryGetHighestPriorityTarget(Character target, out TargetParams targetParams)
851  {
852  targetParams = GetHighestPriorityTarget(target);
853  return targetParams != null;
854  }
855 
856  public bool TryGetHighestPriorityTarget(IEnumerable<Identifier> tags, out TargetParams target)
857  {
858  target = null;
859  if (tags == null || tags.None()) { return false; }
860  float priority = -1;
861  foreach (var potentialTarget in targets)
862  {
863  if (potentialTarget.Priority > priority)
864  {
865  if (tags.Any(t => t == potentialTarget.Tag))
866  {
867  target = potentialTarget;
868  priority = target.Priority;
869  }
870  }
871  }
872  return target != null;
873  }
874  }
875 
876  public class TargetParams : SubParam
877  {
878  public override string Name => "Target";
879 
880  [Serialize("", IsPropertySaveable.Yes, description: "Can be an item tag, species name or something else. Examples: decoy, provocative, light, dead, human, crawler, wall, nasonov, sonar, door, stronger, weaker, light, human, room..."), Editable()]
881  public Identifier Tag { get; private set; }
882 
884  public AIState State { get; set; }
885 
886  [Serialize(0f, IsPropertySaveable.Yes, description: "What base priority is given to the target?"), Editable(minValue: 0f, maxValue: 1000f, ValueStep = 1, DecimalCount = 0)]
887  public float Priority { get; set; }
888 
889  [Serialize(0f, IsPropertySaveable.Yes, description: "Generic distance that can be used for different purposes depending on the state. E.g. in Avoid state this defines the distance that the character tries to keep to the target. If the distance is 0, it's not used."), Editable(MinValueFloat = 0, ValueStep = 10, DecimalCount = 0)]
890  public float ReactDistance { get; set; }
891 
892  [Serialize(0f, IsPropertySaveable.Yes, description: "Used for defining the attack distance for PassiveAggressive and Aggressive states. If the distance is 0, it's not used."), Editable(MinValueFloat = 0, ValueStep = 10, DecimalCount = 0)]
893  public float AttackDistance { get; set; }
894 
895  [Serialize(0f, IsPropertySaveable.Yes, description: "Generic timer that can be used for different purposes depending on the state. E.g. in Observe state this defines how long the character in general keeps staring the targets (Some random is always applied)."), Editable]
896  public float Timer { get; set; }
897 
898  [Serialize(false, IsPropertySaveable.Yes, description: "Should the target be ignored if it's inside a container/inventory. Only affects items."), Editable]
899  public bool IgnoreContained { get; set; }
900 
901  [Serialize(false, IsPropertySaveable.Yes, description: "Should the target be ignored while the creature is inside. Doesn't matter where the target is."), Editable]
902  public bool IgnoreInside { get; set; }
903 
904  [Serialize(false, IsPropertySaveable.Yes, description: "Should the target be ignored while the creature is outside. Doesn't matter where the target is."), Editable]
905  public bool IgnoreOutside { get; set; }
906 
907  [Serialize(false, IsPropertySaveable.Yes, description: "Should the target be ignored if it's inside. Doesn't matter where the creature itself is."), Editable]
908  public bool IgnoreTargetInside { get; set; }
909 
910  [Serialize(false, IsPropertySaveable.Yes, description: "Should the target be ignored if it's outside. Doesn't matter where the creature itself is."), Editable]
911  public bool IgnoreTargetOutside { get; set; }
912 
913  [Serialize(false, IsPropertySaveable.Yes, description: "Should the target be ignored if it's inside a different submarine than us? Normally only some targets are ignored when they are not inside the same sub."), Editable]
914  public bool IgnoreIfNotInSameSub { get; set; }
915 
916  [Serialize(false, IsPropertySaveable.Yes), Editable]
917  public bool IgnoreIncapacitated { get; set; }
918 
919  [Serialize(0f, IsPropertySaveable.Yes, description: "A generic threshold. For example, how much damage the protected target should take from an attacker before the creature starts defending it."), Editable]
920  public float Threshold { get; private set; }
921 
922  [Serialize(-1f, IsPropertySaveable.Yes, description: "A generic min threshold. Not used if set to negative."), Editable]
923  public float ThresholdMin { get; private set; }
924 
925  [Serialize(-1f, IsPropertySaveable.Yes, description: "A generic max threshold. Not used if set to negative."), Editable]
926  public float ThresholdMax { get; private set; }
927 
928  [Serialize(1.0f, IsPropertySaveable.Yes, description: "Can be used to make the monster perceive the target further or closer than it normally can."), Editable]
929  public float PerceptionDistanceMultiplier { get; private set; }
930 
931  [Serialize(-1.0f, IsPropertySaveable.Yes, description: "Maximum distance at which the monster can perceive the target, regardless of the sight/hearing or how visible or how much noise the target is making. Not used if set to negative."), Editable]
932  public float MaxPerceptionDistance { get; private set; }
933 
934  [Serialize("0.0, 0.0", IsPropertySaveable.Yes, description: "A generic offset. Used for example for offsetting the react distance (vector length) and for offsetting the target position when a guardian flees to a pod."), Editable]
935  public Vector2 Offset { get; private set; }
936 
937  [Serialize(AttackPattern.Straight, IsPropertySaveable.Yes, description: "Defines the movement pattern of the character when approaching a target."), Editable]
938  public AttackPattern AttackPattern { get; set; }
939 
940  [Serialize(false, IsPropertySaveable.Yes, description: "If enabled, the AI will give more priority to targets close to the horizontal middle of the sub. Only applies to walls, hulls, and items like sonar. Circle and Sweep always does this regardless of this property."), Editable]
941  public bool PrioritizeSubCenter { get; set; }
942 
943  #region Sweep
944  [Serialize(0f, IsPropertySaveable.Yes, description: "Use to define a distance at which the creature starts the sweeping movement."), Editable(MinValueFloat = 0, MaxValueFloat = 10000, ValueStep = 1, DecimalCount = 0)]
945  public float SweepDistance { get; private set; }
946 
947  [Serialize(10f, IsPropertySaveable.Yes, description: "How much the sweep affects the steering?"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1f, DecimalCount = 1)]
948  public float SweepStrength { get; private set; }
949 
950  [Serialize(1f, IsPropertySaveable.Yes, description: "How quickly the sweep direction changes. Uses the sine wave pattern."), Editable(MinValueFloat = 0, MaxValueFloat = 10, ValueStep = 0.1f, DecimalCount = 2)]
951  public float SweepSpeed { get; private set; }
952  #endregion
953 
954  #region Circle
955  [Serialize(5000f, IsPropertySaveable.Yes, description:"How close to the target the character should be, before they start using the circle pattern instead of directional approaching."), Editable(MinValueFloat = 0f, MaxValueFloat = 20000f)]
956  public float CircleStartDistance { get; private set; }
957 
958  [Serialize(false, IsPropertySaveable.Yes, description:"Normally the target size is taken into account when calculating the distance to the target. Set this true to skip that.")]
959  public bool IgnoreTargetSize { get; private set; }
960 
961  [Serialize(1f, IsPropertySaveable.Yes, description:"Determines the rate how quickly the target movement position is rotated towards the attack target. The actual rotation is calculated once per each attack cycle, based on the current aggression level."), Editable(MinValueFloat = 0f, MaxValueFloat = 100f)]
962  public float CircleRotationSpeed { get; private set; }
963 
964  [Serialize(false, IsPropertySaveable.Yes, description:"When enabled, the circle rotation speed can change when the target is far. When this setting is disabled (default), the character will head directly towards the target when it's too far."), Editable]
965  public bool DynamicCircleRotationSpeed { get; private set; }
966 
967  [Serialize(0f, IsPropertySaveable.Yes, description:"How much the turn speed can differ between attack cycles (stays constant during the cycle)"), Editable(MinValueFloat = 0f, MaxValueFloat = 1f)]
968  public float CircleRandomRotationFactor { get; private set; }
969 
970  [Serialize(5f, IsPropertySaveable.Yes, description:"Affects how close to the target the character has to be before the strike phase of the circle behavior triggers. In the strike phase, the creature moves directly towards the target."), Editable(MinValueFloat = 0f, MaxValueFloat = 10f)]
971  public float CircleStrikeDistanceMultiplier { get; private set; }
972 
973  [Serialize(0f, IsPropertySaveable.Yes, description:"How much the target position is offset at maximum. Low values make the character hit the target earlier/always, higher values make it miss the target when the aggression intensity is low (early in the encounter)."), Editable(MinValueFloat = 0f, MaxValueFloat = 50f)]
974  public float CircleMaxRandomOffset { get; private set; }
975  #endregion
976 
980  public List<PropertyConditional> Conditionals { get; private set; } = new List<PropertyConditional>();
981 
982  public TargetParams(string tag, AIState state, float priority, CharacterParams character) :
983  this(CreateNewElement(character, tag, state, priority), character) { }
984 
985  public TargetParams(ContentXElement element, CharacterParams character) : base(element, character)
986  {
987  foreach (var subElement in element.Elements())
988  {
989  switch (subElement.Name.ToString().ToLowerInvariant())
990  {
991  case "conditional":
992  Conditionals.AddRange(PropertyConditional.FromXElement(subElement));
993  break;
994  }
995  }
996  }
997 
998  public static ContentXElement CreateNewElement(CharacterParams character, Identifier tag, AIState state, float priority) =>
999  CreateNewElement(character, tag.Value, state, priority);
1000 
1001  public static ContentXElement CreateNewElement(CharacterParams character, string tag, AIState state, float priority)
1002  {
1003  return new XElement("target",
1004  new XAttribute("tag", tag),
1005  new XAttribute("state", state),
1006  new XAttribute("priority", priority)).FromPackage(character.File.ContentPackage);
1007  }
1008  }
1009 
1010  public abstract class SubParam : ISerializableEntity
1011  {
1012  public virtual string Name { get; set; }
1013  public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
1014  public ContentXElement Element { get; set; }
1015  public List<SubParam> SubParams { get; set; } = new List<SubParam>();
1016 
1017  public CharacterParams Character { get; private set; }
1018 
1019  protected ContentXElement CreateElement(string name, params object[] attrs)
1020  => new XElement(name, attrs).FromPackage(Element.ContentPackage);
1021 
1022  public SubParam(ContentXElement element, CharacterParams character)
1023  {
1024  Element = element;
1025  Character = character;
1027  }
1028 
1029  public virtual bool Deserialize(bool recursive = true)
1030  {
1032  if (recursive)
1033  {
1034  SubParams.ForEach(sp => sp.Deserialize(true));
1035  }
1036  return SerializableProperties != null;
1037  }
1038 
1039  public virtual bool Serialize(bool recursive = true)
1040  {
1042  if (recursive)
1043  {
1044  SubParams.ForEach(sp => sp.Serialize(true));
1045  }
1046  return true;
1047  }
1048 
1049  public virtual void Reset()
1050  {
1051  // Don't use recursion, because the reset method might be overriden
1052  Deserialize(false);
1053  SubParams.ForEach(sp => sp.Reset());
1054  }
1055 
1056  protected bool RemoveSubParam<T>(T subParam, IList<T> collection = null) where T : SubParam
1057  {
1058  if (subParam == null || subParam.Element == null || subParam.Element.Parent == null) { return false; }
1059  if (collection != null && !collection.Contains(subParam)) { return false; }
1060  if (!SubParams.Contains(subParam)) { return false; }
1061  collection?.Remove(subParam);
1062  SubParams.Remove(subParam);
1063  subParam.Element.Remove();
1064  return true;
1065  }
1066 
1067 #if CLIENT
1069  public virtual void AddToEditor(ParamsEditor editor, bool recursive = true, int space = 0, GUIFont titleFont = null)
1070  {
1071  SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, inGame: false, showName: true, titleFont: titleFont ?? GUIStyle.LargeFont);
1072  if (recursive)
1073  {
1074  SubParams.ForEach(sp => sp.AddToEditor(editor, true, titleFont: titleFont ?? GUIStyle.SmallFont));
1075  }
1076  if (space > 0)
1077  {
1078  new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, space), editor.EditorBox.Content.RectTransform), style: null, color: new Color(20, 20, 20, 255))
1079  {
1080  CanBeFocused = false
1081  };
1082  }
1083  }
1084 #endif
1085  }
1086  #endregion
1087  }
1088 }
bool TryGetHighestPriorityTarget(Character target, out TargetParams targetParams)
bool TryAddEmptyTarget(out TargetParams targetParams)
bool TryGetHighestPriorityTarget(Identifier target, out TargetParams targetParams)
bool TryGetTargets(Identifier target, out IEnumerable< TargetParams > targetParams)
TargetParams GetHighestPriorityTarget(Identifier target)
bool TryGetTargets(Character target, out IEnumerable< TargetParams > targetParams)
IEnumerable< TargetParams > Targets
IEnumerable< TargetParams > GetMatchingTargets(Func< TargetParams, bool > predicate)
IEnumerable< TargetParams > GetTargets(Character target)
bool TryAddNewTarget(string tag, AIState state, float priority, out TargetParams targetParams)
bool TryGetHighestPriorityTarget(IEnumerable< Identifier > tags, out TargetParams target)
TargetParams GetHighestPriorityTarget(Character target)
AIParams(ContentXElement element, CharacterParams character)
bool RemoveTarget(TargetParams target)
IEnumerable< TargetParams > GetTargets(Identifier target)
bool TryAddNewTarget(Identifier tag, AIState state, float priority, out TargetParams targetParams)
IEnumerable< Identifier > ImmunityIdentifiers
HealthParams(ContentXElement element, CharacterParams character)
InventoryItem(ContentXElement element, CharacterParams character)
InventoryParams(ContentXElement element, CharacterParams character)
void AddItem(string identifier=null)
ParticleParams(ContentXElement element, CharacterParams character)
SoundParams(ContentXElement element, CharacterParams character)
ImmutableHashSet< Identifier > TagSet
virtual bool Deserialize(bool recursive=true)
ContentXElement CreateElement(string name, params object[] attrs)
SubParam(ContentXElement element, CharacterParams character)
SerializableEntityEditor SerializableEntityEditor
bool RemoveSubParam< T >(T subParam, IList< T > collection=null)
virtual void AddToEditor(ParamsEditor editor, bool recursive=true, int space=0, GUIFont titleFont=null)
virtual bool Serialize(bool recursive=true)
Dictionary< Identifier, SerializableProperty > SerializableProperties
TargetParams(string tag, AIState state, float priority, CharacterParams character)
List< PropertyConditional > Conditionals
Conditionals that must be met for the character to be able to use these targeting parameters.
static ContentXElement CreateNewElement(CharacterParams character, string tag, AIState state, float priority)
static ContentXElement CreateNewElement(CharacterParams character, Identifier tag, AIState state, float priority)
TargetParams(ContentXElement element, CharacterParams character)
Contains character data that should be editable in the character editor.
bool RemoveBloodEmitter(ParticleParams emitter)
bool RemoveInventory(InventoryParams inventory)
bool RemoveSound(SoundParams soundParams)
bool TryAddSubParam< T >(ContentXElement element, Func< ContentXElement, CharacterParams, T > constructor, out T subParam, IList< T > collection=null, Func< IList< T >, bool > filter=null)
bool RemoveDamageEmitter(ParticleParams emitter)
AIParams AI
Parameters for EnemyAIController. Not used by HumanAIController.
readonly List< ParticleParams > DamageEmitters
override ContentXElement? MainElement
readonly List< SoundParams > Sounds
readonly CharacterFile File
bool Save(string fileNameWithoutExtension=null)
override string GetName()
bool Deserialize(XElement element=null, bool alsoChildren=true, bool recursive=true, bool loadDefaultValues=true)
static XElement CreateVariantXml(ContentXElement variantXML, ContentXElement baseXML)
bool RemoveSubParam< T >(T subParam, IList< T > collection=null)
readonly List< ParticleParams > GibEmitters
void AddToEditor(ParamsEditor editor, bool alsoChildren=true, bool recursive=true, int space=0)
readonly List< ParticleParams > BloodEmitters
CharacterParams(CharacterFile file)
bool Serialize(XElement element=null, bool alsoChildren=true, bool recursive=true)
static bool CompareGroup(Identifier group1, Identifier group2)
bool HasTag(Identifier tag)
readonly List< InventoryParams > Inventories
readonly List< SubParam > SubParams
override bool Reset(bool forceReload=false)
bool RemoveGibEmitter(ParticleParams emitter)
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
ContentXElement ConfigElement
readonly ContentPackage ContentPackage
Definition: ContentFile.cs:136
void Add(ContentXElement elem)
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
ContentPackage? ContentPackage
IEnumerable< ContentXElement > GetChildElements(string name)
ContentXElement? GetChildElement(string name)
Identifier GetAttributeIdentifier(string key, string def)
virtual void UpdatePath(ContentPath fullPath)
ContentXElement CreateElement(string name, params object[] attrs)
ContentXElement OriginalElement
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
Conditionals are used by some in-game mechanics to require one or more conditions to be met for those...
static IEnumerable< PropertyConditional > FromXElement(ContentXElement element, Predicate< XAttribute >? predicate=null)
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)
static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault=false, bool ignoreEditable=false)