Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.Linq;
6 using System.Xml.Linq;
7 
8 namespace Barotrauma
9 {
11  {
13 
14  public class ChildObject
15  {
16  public List<string> AllowedNames;
17  public int MinCount, MaxCount;
18 
19  public ChildObject()
20  {
21  AllowedNames = new List<string>();
22  MinCount = 1;
23  MaxCount = 1;
24  }
25 
26  public ChildObject(XElement element)
27  {
28  AllowedNames = element.GetAttributeStringArray("names", Array.Empty<string>()).ToList();
29  MinCount = element.GetAttributeInt("mincount", 1);
30  MaxCount = Math.Max(element.GetAttributeInt("maxcount", 1), MinCount);
31  }
32  }
33 
34  [Flags]
35  public enum SpawnPosType
36  {
37  None = 0,
38  MainPathWall = 1,
39  SidePathWall = 2,
40  CaveWall = 4,
41  NestWall = 8,
42  RuinWall = 16,
43  SeaFloor = 32,
44  MainPath = 64,
45  LevelStart = 128,
46  LevelEnd = 256,
47  OutpostWall = 512,
48  Wall = MainPathWall | SidePathWall | CaveWall,
49  }
50 
51  public List<Sprite> Sprites
52  {
53  get;
54  private set;
55  } = new List<Sprite>();
56 
58  {
59  get;
60  private set;
61  }
62 
63  [Serialize(1.0f, IsPropertySaveable.No), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
64  public float MinSize
65  {
66  get;
67  private set;
68  }
69  [Serialize(1.0f, IsPropertySaveable.No), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
70  public float MaxSize
71  {
72  get;
73  private set;
74  }
75 
79  [Serialize((Alignment.Top | Alignment.Bottom | Alignment.Left | Alignment.Right), IsPropertySaveable.Yes, description: "Which sides of a wall the object can spawn on."), Editable]
81  {
82  get;
83  private set;
84  }
85 
88  {
89  get;
90  private set;
91  }
92 
93  public XElement Config
94  {
95  get;
96  private set;
97  }
98 
99  public readonly List<ContentXElement> LevelTriggerElements;
100 
105  public readonly Dictionary<Identifier, float> OverrideCommonness;
106 
107  public XElement PhysicsBodyElement
108  {
109  get;
110  private set;
111  }
113  {
114  get;
115  private set;
116  } = -1;
117 
118  public Dictionary<Sprite, XElement> SpriteSpecificPhysicsBodyElements
119  {
120  get;
121  private set;
122  } = new Dictionary<Sprite, XElement>();
123 
124 
125  [Serialize(10000, IsPropertySaveable.No, description: "Maximum number of this specific object per level."), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
126  public int MaxCount
127  {
128  get;
129  private set;
130  }
131 
132  [Serialize("0.0,1.0", IsPropertySaveable.Yes, description: "The sprite depth of the object (min, max). Values of 0 or less make the object render in front of walls, values larger than 0 make it render behind walls with a parallax effect."), Editable]
133  public Vector2 DepthRange
134  {
135  get;
136  private set;
137  }
138 
139  [Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f),
140  Serialize(0.0f, IsPropertySaveable.Yes, description: "The tendency for the prefab to form clusters. Used as an exponent for perlin noise values that are used to determine the probability for an object to spawn at a specific position.")]
145  public float ClusteringAmount
146  {
147  get;
148  private set;
149  }
150 
151  [Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f),
152  Serialize(0.0f, IsPropertySaveable.Yes, description: "A value between 0-1 that determines the z-coordinate to sample perlin noise from when determining the probability " +
153  " for an object to spawn at a specific position. Using the same (or close) value for different objects means the objects tend " +
154  "to form clusters in the same areas.")]
161  public float ClusteringGroup
162  {
163  get;
164  private set;
165  }
166 
167  [Editable, Serialize("0,0", IsPropertySaveable.Yes, description: "Random offset from the surface the object spawns on.")]
168  public Vector2 RandomOffset
169  {
170  get;
171  private set;
172  }
173 
174  [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Should the object be rotated to align it with the wall surface it spawns on.")]
175  public bool AlignWithSurface
176  {
177  get;
178  private set;
179  }
180 
181  [Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the object be placed near the start of the level.")]
182  public bool AllowAtStart
183  {
184  get;
185  private set;
186  }
187 
188  [Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the object be placed near the end of the level.")]
189  public bool AllowAtEnd
190  {
191  get;
192  private set;
193  }
194 
195  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Minimum length of a graph edge the object can spawn on."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
199  public float MinSurfaceWidth
200  {
201  get;
202  private set;
203  }
204 
205  private Vector2 randomRotation;
206  [Editable, Serialize("0.0,0.0", IsPropertySaveable.Yes, description: "How much the rotation of the object can vary (min and max values in degrees).")]
207  public Vector2 RandomRotation
208  {
209  get { return new Vector2(MathHelper.ToDegrees(randomRotation.X), MathHelper.ToDegrees(randomRotation.Y)); }
210  private set
211  {
212  randomRotation = new Vector2(MathHelper.ToRadians(value.X), MathHelper.ToRadians(value.Y));
213  }
214  }
215 
216  public Vector2 RandomRotationRad => randomRotation;
217 
218  private float swingAmount;
219  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How much the object swings (in degrees)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 360.0f)]
220  public float SwingAmount
221  {
222  get { return MathHelper.ToDegrees(swingAmount); }
223  private set
224  {
225  swingAmount = MathHelper.ToRadians(value);
226  }
227  }
228 
229  public float SwingAmountRad => swingAmount;
230 
231  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How fast the object swings."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
232  public float SwingFrequency
233  {
234  get;
235  private set;
236  }
237 
238  [Editable, Serialize("0.0,0.0", IsPropertySaveable.Yes, description: "How much the scale of the object oscillates on each axis. A value of 0.5,0.5 would make the object's scale oscillate from 100% to 150%.")]
239  public Vector2 ScaleOscillation
240  {
241  get;
242  private set;
243  }
244 
245  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How fast the object's scale oscillates."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
247  {
248  get;
249  private set;
250  }
251 
252  [Editable, Serialize(1.0f, IsPropertySaveable.Yes, description: "How likely it is for the object to spawn in a level. " +
253  "This is relative to the commonness of the other objects - for example, having an object with " +
254  "a commonness of 1 and another with a commonness of 10 would mean the latter appears in levels 10 times as frequently as the former. " +
255  "The commonness value can be overridden on specific level types.")]
256  public float Commonness
257  {
258  get;
259  private set;
260  }
261 
262  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How much the object disrupts submarine's sonar."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
263  public float SonarDisruption
264  {
265  get;
266  private set;
267  }
268 
269  [Serialize(false, IsPropertySaveable.Yes, description: "Can the object take damage from weapons/attacks that damage level walls."), Editable]
271  {
272  get;
273  private set;
274  }
275 
276  [Serialize(false, IsPropertySaveable.Yes, description: "Should the object disappear if the object is destroyed? Only relevant if TakeLevelWallDamage is true."), Editable]
277  public bool HideWhenBroken
278  {
279  get;
280  private set;
281  }
282 
283  [Serialize(100.0f, IsPropertySaveable.Yes, description: "Amount of health the object has. Only relevant if TakeLevelWallDamage is true."), Editable]
284  public float Health
285  {
286  get;
287  private set;
288  }
289 
290  [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable]
291  public Color SpriteColor
292  {
293  get;
294  private set;
295  }
296 
297  public string Name => Identifier.Value;
298 
299  public List<ChildObject> ChildObjects
300  {
301  get;
302  private set;
303  }
304 
305  public Dictionary<Identifier, SerializableProperty> SerializableProperties
306  {
307  get; private set;
308  }
309 
314  public List<LevelObjectPrefab> OverrideProperties
315  {
316  get;
317  private set;
318  }
319 
320  public override string ToString()
321  {
322  return "LevelObjectPrefab (" + Identifier + ")";
323  }
324 
325 
326  public LevelObjectPrefab(ContentXElement element, LevelObjectPrefabsFile file, Identifier identifierOverride = default) : base(file, ParseIdentifier(identifierOverride, element))
327  {
328  ChildObjects = new List<ChildObject>();
329  LevelTriggerElements = new List<ContentXElement>();
330  OverrideProperties = new List<LevelObjectPrefab>();
331  OverrideCommonness = new Dictionary<Identifier, float>();
332 
334  if (element != null)
335  {
336  Config = element;
337 
338  LoadElements(file, element, -1);
339  InitProjSpecific(element);
340  }
341 
342  //use the maximum width of the sprite as the minimum surface width if no value is given
343  if (element != null && !element.Attributes("minsurfacewidth").Any())
344  {
345  if (Sprites.Any()) MinSurfaceWidth = Sprites[0].size.X * MaxSize;
347  }
348  }
349 
350  public static Identifier ParseIdentifier(Identifier identifierOverride, XElement element)
351  {
352  if (!identifierOverride.IsEmpty) { return identifierOverride; }
353  Identifier identifier = element.GetAttributeIdentifier("identifier", "");
354  if (identifier.IsEmpty)
355  {
356 #if DEBUG
357  DebugConsole.ThrowError($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
358 #else
359  DebugConsole.AddWarning($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
360 #endif
361  identifier = element.NameAsIdentifier();
362  }
363  return identifier;
364  }
365 
366  private void LoadElements(LevelObjectPrefabsFile file, ContentXElement element, int parentTriggerIndex)
367  {
368  int propertyOverrideCount = 0;
369  //load sprites first, OverrideProperties may need them (defaulting to the default sprite if no override is defined)
370  foreach (var subElement in element.Elements())
371  {
372  switch (subElement.Name.ToString().ToLowerInvariant())
373  {
374  case "sprite":
375  var newSprite = new Sprite(subElement, lazyLoad: true);
376  Sprites.Add(newSprite);
377  var spriteSpecificPhysicsBodyElement =
378  subElement.GetChildElement("PhysicsBody") ?? subElement.GetChildElement("Body") ??
379  subElement.GetChildElement("physicsbody") ?? subElement.GetChildElement("body");
380  if (spriteSpecificPhysicsBodyElement != null)
381  {
382  SpriteSpecificPhysicsBodyElements.Add(newSprite, spriteSpecificPhysicsBodyElement);
383  }
384  break;
385  case "deformablesprite":
386  DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
387  break;
388  }
389  }
390  foreach (var subElement in element.Elements())
391  {
392  switch (subElement.Name.ToString().ToLowerInvariant())
393  {
394  case "overridecommonness":
395  Identifier levelType = subElement.GetAttributeIdentifier("leveltype", Identifier.Empty);
396  if (!OverrideCommonness.ContainsKey(levelType))
397  {
398  OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f));
399  }
400  break;
401  case "leveltrigger":
402  case "trigger":
403  OverrideProperties.Add(null);
404  LevelTriggerElements.Add(subElement);
405  LoadElements(file, subElement, LevelTriggerElements.Count - 1);
406  break;
407  case "childobject":
408  ChildObjects.Add(new ChildObject(subElement));
409  break;
410  case "overrideproperties":
411  var propertyOverride = new LevelObjectPrefab(subElement, file, identifierOverride: $"{Identifier}-{propertyOverrideCount}".ToIdentifier());
412  OverrideProperties[OverrideProperties.Count - 1] = propertyOverride;
413  if (!propertyOverride.Sprites.Any() && propertyOverride.DeformableSprite == null)
414  {
415  propertyOverride.Sprites = Sprites;
416  propertyOverride.DeformableSprite = DeformableSprite;
417  }
418  propertyOverrideCount++;
419  break;
420  case "body":
421  case "physicsbody":
422  PhysicsBodyElement = subElement;
423  PhysicsBodyTriggerIndex = parentTriggerIndex;
424  break;
425  }
426  }
427  }
428 
429  partial void InitProjSpecific(ContentXElement element);
430 
431 
432  public float GetCommonness(CaveGenerationParams generationParams, bool requireCaveSpecificOverride = true)
433  {
434  if (generationParams != null &&
435  generationParams.Identifier != Identifier.Empty &&
436  OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness))
437  {
438  return commonness;
439  }
440  return requireCaveSpecificOverride ? 0.0f : Commonness;
441  }
442 
443  public float GetCommonness(LevelData levelData)
444  {
445  if (levelData.GenerationParams != null && levelData.GenerationParams.Identifier != Identifier.Empty &&
446  OverrideCommonness.TryGetValue(levelData.GenerationParams.Identifier, out float commonness) ||
447  (!levelData.GenerationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(levelData.GenerationParams.OldIdentifier, out commonness)))
448  {
449  return commonness;
450  }
451  if (levelData?.Biome != null)
452  {
453  if (OverrideCommonness.TryGetValue(levelData.Biome.Identifier, out float biomeCommonness))
454  {
455  return biomeCommonness;
456  }
457  }
458  return Commonness;
459  }
460 
461  public override void Dispose() { }
462  }
463 }
IEnumerable< XAttribute > Attributes()
IEnumerable< ContentXElement > Elements()
LevelGenerationParams GenerationParams
Definition: LevelData.cs:27
readonly Biome Biome
Definition: LevelData.cs:25
List< LevelObjectPrefab > OverrideProperties
A list of prefabs whose properties override this one's properties when a trigger is active....
LevelObjectPrefab(ContentXElement element, LevelObjectPrefabsFile file, Identifier identifierOverride=default)
float GetCommonness(CaveGenerationParams generationParams, bool requireCaveSpecificOverride=true)
float ClusteringGroup
A value between 0-1 that determines the z-coordinate to sample perlin noise from when determining the...
float ClusteringAmount
The tendency for the prefab to form clusters. Used as an exponent for perlin noise values that are us...
readonly Dictionary< Identifier, float > OverrideCommonness
Overrides the commonness of the object in a specific level type. Key = name of the level type,...
float MinSurfaceWidth
Minimum length of a graph edge the object can spawn on.
static Identifier ParseIdentifier(Identifier identifierOverride, XElement element)
Alignment Alignment
Which sides of a wall the object can appear on.
readonly Identifier Identifier
Definition: Prefab.cs:34
Prefab that has a property serves as a deterministic hash of a prefab's identifier....
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)