Client LuaCsForBarotrauma
LevelGenerationParams.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Linq;
7 
8 namespace Barotrauma
9 {
11  {
13 
14  public LocalizedString DisplayName { get; private set; }
15  public LocalizedString Description { get; private set; }
16 
17  public string Name => Identifier.Value;
18 
19  public Identifier OldIdentifier { get; }
20 
21  private int minWidth, maxWidth, height;
22 
23  private Point voronoiSiteInterval;
24  //how much the sites are "scattered" on x- and y-axis
25  //if Vector2.Zero, the sites will just be placed in a regular grid pattern
26  private Point voronoiSiteVariance;
27 
28  //how far apart the nodes of the main path can be
29  //x = min interval, y = max interval
30  private Point mainPathNodeIntervalRange;
31 
32  private int caveCount;
33 
34  //how large portion of the bottom of the level should be "carved out"
35  //if 0.0f, the bottom will be completely solid (making the abyss unreachable)
36  //if 1.0f, the bottom will be completely open
37  private float bottomHoleProbability;
38 
39  //the y-position of the ocean floor (= the position from which the bottom formations extend upwards)
40  private int seaFloorBaseDepth;
41  //how much random variance there can be in the height of the formations
42  private int seaFloorVariance;
43 
44  private int cellSubdivisionLength;
45  private float cellRoundingAmount;
46  private float cellIrregularity;
47 
48  private int mountainCountMin, mountainCountMax;
49 
50  private int mountainHeightMin, mountainHeightMax;
51 
52  private float waterParticleScale;
53 
54  private int initialDepthMin, initialDepthMax;
55 
56  //which biomes can this type of level appear in
57  public readonly ImmutableHashSet<Identifier> AllowedBiomeIdentifiers;
58  public readonly bool AnyBiomeAllowed;
59 
60  public Dictionary<Identifier, SerializableProperty> SerializableProperties
61  {
62  get;
63  set;
64  }
65 
66  [Header("General")]
67  [Serialize(LevelData.LevelType.LocationConnection, IsPropertySaveable.Yes), Editable]
69  {
70  get;
71  set;
72  }
73 
74  [Serialize(false, IsPropertySaveable.Yes, "If the given level is only used in PvP modes"), Editable]
75  public bool IsPvPLevel { get; set; }
76 
77  [Serialize(100.0f, IsPropertySaveable.Yes, "If there are multiple level generation parameters available for a level in a given biome, their commonness determines how likely it is for one to get selected."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
78  public float Commonness
79  {
80  get;
81  set;
82  }
83 
84  [Serialize(false, IsPropertySaveable.Yes, "If the level is a transition from the previous biome to this one."), Editable]
85  public bool TransitionFromPreviousBiome { get; set; }
86 
87  [Serialize(0.0f, IsPropertySaveable.Yes, "The difficulty of the level has to be above or equal to this for these parameters to get chosen for the level."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
88  public float MinLevelDifficulty
89  {
90  get;
91  set;
92  }
93 
94  [Serialize(100.0f, IsPropertySaveable.Yes, "The difficulty of the level has to be below or equal to this for these parameters to get chosen for the level."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
95  public float MaxLevelDifficulty
96  {
97  get;
98  set;
99  }
100 
101  private Vector2 startPosition;
102 
103  [Header("Layout")]
104  [Serialize("0,0", IsPropertySaveable.Yes, "Start position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
105  public Vector2 StartPosition
106  {
107  get { return startPosition; }
108  set
109  {
110  startPosition = new Vector2(
111  MathHelper.Clamp(value.X, 0.0f, 1.0f),
112  MathHelper.Clamp(value.Y, 0.0f, 1.0f));
113  }
114  }
115 
116  private Vector2 endPosition;
117  [Serialize("1,0", IsPropertySaveable.Yes, "End position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
118  public Vector2 EndPosition
119  {
120  get { return endPosition; }
121  set
122  {
123  endPosition = new Vector2(
124  MathHelper.Clamp(value.X, 0.0f, 1.0f),
125  MathHelper.Clamp(value.Y, 0.0f, 1.0f));
126  }
127  }
128 
129  private Vector2 forceOutpostPosition;
130  [Serialize("0,0", IsPropertySaveable.Yes, "Position of the outpost (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner). If set to 0,0, the outpost is placed in a suitable position automatically."), Editable(DecimalCount = 2)]
131  public Vector2 ForceOutpostPosition
132  {
133  get { return forceOutpostPosition; }
134  set
135  {
136  forceOutpostPosition = new Vector2(
137  MathHelper.Clamp(value.X, 0.0f, 1.0f),
138  MathHelper.Clamp(value.Y, 0.0f, 1.0f));
139  }
140  }
141 
142  [Serialize(true, IsPropertySaveable.Yes, "Should there be a hole in the wall next to the end outpost (can be used to prevent players from having to backtrack if they approach the outpost from the wrong side of the main path's walls)."), Editable]
144  {
145  get;
146  set;
147  }
148 
149  [Serialize(0.4f, IsPropertySaveable.Yes, description: "The probability for wall cells to be removed from the bottom of the map. A value of 0 will produce a completely enclosed tunnel and 1 will make the entire bottom of the level completely open."), Editable()]
151  {
152  get { return bottomHoleProbability; }
153  set { bottomHoleProbability = MathHelper.Clamp(value, 0.0f, 1.0f); }
154  }
155 
156  [Serialize(100000, IsPropertySaveable.Yes), Editable]
157  public int MinWidth
158  {
159  get { return minWidth; }
160  set { minWidth = MathHelper.Clamp(value, 2000, 1000000); }
161  }
162 
163  [Serialize(100000, IsPropertySaveable.Yes), Editable]
164  public int MaxWidth
165  {
166  get { return maxWidth; }
167  set { maxWidth = MathHelper.Clamp(value, 2000, 1000000); }
168  }
169 
170  [Serialize(50000, IsPropertySaveable.Yes), Editable]
171  public int Height
172  {
173  get { return height; }
174  set { height = MathHelper.Clamp(value, 2000, 1000000); }
175  }
176 
177  [Serialize(80000, IsPropertySaveable.Yes, description: "Minimum depth at the top of the level (100 corresponds to 1 meter)."), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
178  public int InitialDepthMin
179  {
180  get { return initialDepthMin; }
181  set { initialDepthMin = Math.Max(value, 0); }
182  }
183 
184  [Serialize(80000, IsPropertySaveable.Yes, description: "Maximum depth at the top of the level (100 corresponds to 1 meter)."), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
185  public int InitialDepthMax
186  {
187  get { return initialDepthMax; }
188  set { initialDepthMax = Math.Max(value, initialDepthMin); }
189  }
190 
191  [Header("Level geometry")]
192 
193  [Serialize(false, IsPropertySaveable.Yes, description: "If enabled, no walls generate in the level. Can be useful for e.g. levels that are just supposed to consist of a pre-built outpost."), Editable]
194  public bool NoLevelGeometry
195  {
196  get;
197  set;
198  }
199 
200  [Editable, Serialize("3000, 3000", IsPropertySaveable.Yes, description: "How far from each other voronoi sites are placed. " +
201  "Sites determine shape of the voronoi graph which the level walls are generated from. " +
202  "(Decreasing this value causes the number of sites, and the complexity of the level, to increase exponentially - be careful when adjusting)")]
203  public Point VoronoiSiteInterval
204  {
205  get { return voronoiSiteInterval; }
206  set
207  {
208  voronoiSiteInterval.X = MathHelper.Clamp(value.X, 100, MinWidth / 2);
209  voronoiSiteInterval.Y = MathHelper.Clamp(value.Y, 100, height / 2);
210  }
211  }
212 
213  [Editable, Serialize("700,700", IsPropertySaveable.Yes, description: "How much random variation to apply to the positions of the voronoi sites on each axis. " +
214  "Small values produce roughly rectangular level walls. The larger the values are, the less uniform the shapes get.")]
215  public Point VoronoiSiteVariance
216  {
217  get { return voronoiSiteVariance; }
218  set
219  {
220  voronoiSiteVariance = new Point(
221  MathHelper.Clamp(value.X, 0, voronoiSiteInterval.X),
222  MathHelper.Clamp(value.Y, 0, voronoiSiteInterval.Y));
223  }
224  }
225 
226  [Editable(MinValueInt = 500, MaxValueInt = 10000), Serialize(5000, IsPropertySaveable.Yes, description: "The edges of the individual wall cells are subdivided into edges of this size. "
227  + "Can be used in conjunction with the rounding values to make the cells rounder. Smaller values will make the cells look smoother, " +
228  "but make the level more performance-intensive as the number of polygons used in rendering and physics calculations increases.")]
230  {
231  get { return cellSubdivisionLength; }
232  set
233  {
234  cellSubdivisionLength = Math.Max(value, 10);
235  }
236  }
237 
238 
239  [Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f), Serialize(0.5f, IsPropertySaveable.Yes, description: "How much the individual wall cells are rounded. "
240  + "Note that the final shape of the cells is also affected by the CellSubdivisionLength parameter.")]
241  public float CellRoundingAmount
242  {
243  get { return cellRoundingAmount; }
244  set
245  {
246  cellRoundingAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
247  }
248  }
249 
250  [Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f), Serialize(0.1f, IsPropertySaveable.Yes, description: "How much random variance is applied to the edges of the cells. "
251  + "Note that the final shape of the cells is also affected by the CellSubdivisionLength parameter.")]
252  public float CellIrregularity
253  {
254  get { return cellIrregularity; }
255  set
256  {
257  cellIrregularity = MathHelper.Clamp(value, 0.0f, 1.0f);
258  }
259  }
260 
261 
262  [Header("Tunnels")]
263  [Serialize(6500, IsPropertySaveable.Yes, description: "Minimum width of the main tunnel going through the level, in pixels. Can be automatically increased by the level editor if the submarine is larger than this."), Editable(MinValueInt = 5000, MaxValueInt = 1000000)]
264  public int MinTunnelRadius
265  {
266  get;
267  set;
268  }
269 
270 
271  [Serialize("0,1", IsPropertySaveable.Yes, description: "Amount of side tunnels in the level (min,max)."), Editable]
272  public Point SideTunnelCount
273  {
274  get;
275  set;
276  }
277 
278 
279  [Serialize(0.5f, IsPropertySaveable.Yes, description: "How much the side tunnels can \"zigzag\". 0 = completely straight tunnel, 1 = can go all the way from the top of the level to the bottom."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
280  public float SideTunnelVariance
281  {
282  get;
283  set;
284  }
285 
286  [Serialize("2000,6000", IsPropertySaveable.Yes, description: "Minimum width of the side tunnels, in pixels. Unlike the main tunnel, does not get adjusted based on the size of the submarine."), Editable]
287  public Point MinSideTunnelRadius
288  {
289  get;
290  set;
291  }
292 
293  [Editable(VectorComponentLabels = new string[] { "editable.minvalue", "editable.maxvalue" }),
294  Serialize("5000, 10000", IsPropertySaveable.Yes, description: "The distance between the nodes that are used to generate the main path through the level (min, max). Larger values produce a straighter path.")]
296  {
297  get { return mainPathNodeIntervalRange; }
298  set
299  {
300  mainPathNodeIntervalRange.X = MathHelper.Clamp(value.X, 100, MinWidth / 2);
301  mainPathNodeIntervalRange.Y = MathHelper.Clamp(value.Y, mainPathNodeIntervalRange.X, MinWidth / 2);
302  }
303  }
304 
305  [Serialize(0.5f, IsPropertySaveable.Yes, description: "How much the side tunnels can \"zigzag\". 0 = completely straight tunnel, 1 = can go all the way from the top of the level to the bottom."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
306  public float MainPathVariance
307  {
308  get;
309  set;
310  }
311 
312  [Header("Contents")]
313  [Serialize(1000, IsPropertySaveable.Yes, description: "The total number of level objects (vegetation, vents, etc) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100000)]
314  public int LevelObjectAmount
315  {
316  get;
317  set;
318  }
319 
320  [Serialize(80, IsPropertySaveable.Yes, description: "The total number of decorative background creatures."), Editable(MinValueInt = 0, MaxValueInt = 1000)]
322  {
323  get;
324  set;
325  }
326 
327 
328  [Editable, Serialize(5, IsPropertySaveable.Yes, description: "The number of caves placed along the main path.")]
329  public int CaveCount
330  {
331  get { return caveCount; }
332  set { caveCount = MathHelper.Clamp(value, 0, 100); }
333  }
334 
335  [Serialize(100, IsPropertySaveable.Yes, description: "The maximum number of level resources in the level."), Editable(MinValueInt = 0, MaxValueInt = 10000)]
336  public int ItemCount
337  {
338  get;
339  set;
340  }
341 
342  [Serialize("19200,38400", IsPropertySaveable.Yes, description: "The minimum and maximum distance between two resource spawn points on a path."), Editable(100, 100000)]
344  {
345  get;
346  set;
347  }
348 
349  [Serialize("9600,19200", IsPropertySaveable.Yes, description: "The minimum and maximum distance between two resource spawn points on a cave path."), Editable(100, 100000)]
351  {
352  get;
353  set;
354  }
355 
356  [Serialize("3,6", IsPropertySaveable.Yes, description: "The minimum and maximum amount of resources in a single cluster. " +
357  "In addition to this, resource commonness affects the cluster size. Less common resources spawn in smaller clusters."), Editable(1, 20)]
359  {
360  get;
361  set;
362  }
363 
364  [Serialize(0.3f, IsPropertySaveable.Yes, description: "How likely a resource spawn point on a path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
365  public float ResourceSpawnChance { get; set; }
366 
367  [Serialize(1.0f, IsPropertySaveable.Yes, description: "How likely a resource spawn point on a cave path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
368  public float CaveResourceSpawnChance { get; set; }
369 
370  [Serialize(0, IsPropertySaveable.Yes, description: "Number of floating, destructible ice chunks in the level."), Editable(MinValueInt = 0, MaxValueInt = 20)]
372  {
373  get;
374  set;
375  }
376 
377  [Serialize(0, IsPropertySaveable.Yes, description: "Number of islands (static wall chunks along the main path) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100)]
378  public int IslandCount
379  {
380  get;
381  set;
382  }
383 
384  [Serialize(0, IsPropertySaveable.Yes, description: "Number of ice spires in the level."), Editable(MinValueInt = 0, MaxValueInt = 20)]
385  public int IceSpireCount
386  {
387  get;
388  set;
389  }
390 
391  [Header("Abyss")]
392  [Serialize(true, IsPropertySaveable.Yes, "Should the generator force a hole to the bottom of the level to ensure there's a way to the abyss."), Editable]
393  public bool CreateHoleToAbyss
394  {
395  get;
396  set;
397  }
398 
399  [Serialize(5, IsPropertySaveable.Yes, description: "Number of abyss islands in the level."), Editable(MinValueInt = 0, MaxValueInt = 20)]
400  public int AbyssIslandCount
401  {
402  get;
403  set;
404  }
405 
406  [Serialize("4000,7000", IsPropertySaveable.Yes), Editable]
407  public Point AbyssIslandSizeMin
408  {
409  get;
410  set;
411  }
412 
413  [Serialize("8000,10000", IsPropertySaveable.Yes), Editable]
414  public Point AbyssIslandSizeMax
415  {
416  get;
417  set;
418  }
419 
420  [Serialize(0.5f, IsPropertySaveable.Yes, description: "The probability of an abyss island having a cave. There is always a cave in at least one of the islands regardless of this setting."), Editable()]
422  {
423  get;
424  set;
425  }
426 
427  [Serialize(10, IsPropertySaveable.Yes, description: "Minimum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
429  {
430  get;
431  set;
432  }
433 
434  [Serialize(40, IsPropertySaveable.Yes, description: "Maximum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
436  {
437  get;
438  set;
439  }
440 
441  [Header("Sea floor")]
442  [Serialize(-300000, IsPropertySaveable.Yes, description: "How far below the level the sea floor is placed."), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f)]
443  public int SeaFloorDepth
444  {
445  get { return seaFloorBaseDepth; }
446  set { seaFloorBaseDepth = MathHelper.Clamp(value, Level.MaxEntityDepth, 0); }
447  }
448 
449  [Serialize(1000, IsPropertySaveable.Yes, description: "Variance of the depth of the sea floor. Smaller values produce a smoother sea floor."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100000.0f)]
450  public int SeaFloorVariance
451  {
452  get { return seaFloorVariance; }
453  set { seaFloorVariance = value; }
454  }
455 
456  [Serialize(0, IsPropertySaveable.Yes, description: "The minimum number of mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 20)]
457  public int MountainCountMin
458  {
459  get { return mountainCountMin; }
460  set
461  {
462  mountainCountMin = Math.Max(value, 0);
463  }
464  }
465 
466  [Serialize(0, IsPropertySaveable.Yes, description: "The maximum number of mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 20)]
467  public int MountainCountMax
468  {
469  get { return mountainCountMax; }
470  set
471  {
472  mountainCountMax = Math.Max(value, 0);
473  }
474  }
475 
476  [Serialize(1000, IsPropertySaveable.Yes, description: "The minimum height of the mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
477  public int MountainHeightMin
478  {
479  get { return mountainHeightMin; }
480  set
481  {
482  mountainHeightMin = Math.Max(value, 0);
483  }
484  }
485 
486  [Serialize(5000, IsPropertySaveable.Yes, description: "The maximum height of the mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
487  public int MountainHeightMax
488  {
489  get { return mountainHeightMax; }
490  set
491  {
492  mountainHeightMax = Math.Max(value, 0);
493  }
494  }
495 
496  public bool UseRandomRuinCount() => MinRuinCount >= 0 && MaxRuinCount > 0;
497 
499 
500  [Header("Ruins")]
501  [Serialize(1, IsPropertySaveable.Yes, description: "The number of alien ruins in the level. Ignored, if both MinRuinCount and MaxRuinCount are defined."), Editable(MinValueInt = 0, MaxValueInt = 10)]
502  public int RuinCount { get; set; }
503 
504  [Serialize(0, IsPropertySaveable.Yes, description: "The minimum number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
505  public int MinRuinCount { get; set; }
506 
507  [Serialize(0, IsPropertySaveable.Yes, description: "The maximum number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
508  public int MaxRuinCount { get; set; }
509 
510  [Serialize(1.0f, IsPropertySaveable.Yes, description: "The probability of spawning a ruin in the level. If the level can have multiple ruins, the probability is evaluated separately for each."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2)]
511  public float RuinSpawnProbability { get; set; }
512 
513  // TODO: Move the wreck parameters under a separate class?
514 #region Wreck parameters
515  [Header("Wrecks")]
516  [Serialize(1, IsPropertySaveable.Yes, description: "The minimum number of wrecks in the level. Note that this value cannot be higher than the amount of wreck prefabs (subs)."), Editable(MinValueInt = 0, MaxValueInt = 10)]
517  public int MinWreckCount { get; set; }
518 
519  [Serialize(1, IsPropertySaveable.Yes, description: "The maximum number of wrecks in the level. Note that this value cannot be higher than the amount of wreck prefabs (subs)."), Editable(MinValueInt = 0, MaxValueInt = 10)]
520  public int MaxWreckCount { get; set; }
521 
522  [Serialize(1, IsPropertySaveable.Yes, description: "The minimum number of corpses per wreck."), Editable(MinValueInt = 0, MaxValueInt = 20)]
523  public int MinCorpseCount { get; set; }
524 
525  [Serialize(5, IsPropertySaveable.Yes, description: "The maximum number of corpses per wreck."), Editable(MinValueInt = 0, MaxValueInt = 20)]
526  public int MaxCorpseCount { get; set; }
527 
528  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How likely is it that a character set to be spawned as a corpse spawns as a human husk instead? Percentage from 0 to 1 per character."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
529  public float HuskProbability { get; set; }
530 
531  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How likely is it that a Thalamus inhabits a wreck. Percentage from 0 to 1 per wreck."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
532  public float ThalamusProbability { get; set; }
533 
534  [Serialize(0.5f, IsPropertySaveable.Yes, description: "How likely the water level of a hull inside a wreck is randomly set."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
535  public float WreckHullFloodingChance { get; set; }
536 
537  [Serialize(0.1f, IsPropertySaveable.Yes, description: "The min water percentage of randomly flooding hulls in wrecks."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
538  public float WreckFloodingHullMinWaterPercentage { get; set; }
539 
540  [Serialize(1.0f, IsPropertySaveable.Yes, description: "The min water percentage of randomly flooding hulls in wrecks."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
541  public float WreckFloodingHullMaxWaterPercentage { get; set; }
542  #endregion
543 
544  [Serialize("", IsPropertySaveable.Yes, description: "Should a beacon station always spawn in this type of level?")]
545  public string ForceBeaconStation { get; set; }
546 
547  [Header("Visuals")]
548  [Serialize(1.0f, IsPropertySaveable.Yes, description: "Scale of the water particle texture."), Editable]
549  public float WaterParticleScale
550  {
551  get { return waterParticleScale; }
552  private set { waterParticleScale = Math.Max(value, 0.01f); }
553  }
554 
555  private Vector2 waterParticleVelocity;
556  [Serialize("0,10", IsPropertySaveable.Yes, description: "How fast the water particle texture scrolls."), Editable]
557  public Vector2 WaterParticleVelocity
558  {
559  get { return waterParticleVelocity; }
560  private set { waterParticleVelocity = value; }
561  }
562 
563  [Serialize(2048.0f, IsPropertySaveable.Yes, description: "Size of the level wall texture."), Editable(minValue: 10.0f, maxValue: 10000.0f)]
564  public float WallTextureSize
565  {
566  get;
567  private set;
568  }
569 
570  [Serialize(2048.0f, IsPropertySaveable.Yes), Editable(minValue: 10.0f, maxValue: 10000.0f)]
571  public float WallEdgeTextureWidth
572  {
573  get;
574  private set;
575  }
576 
577  [Serialize("0,0", IsPropertySaveable.Yes, description: "Interval of lightning-like flashes of light in the level."), Editable]
578  public Vector2 FlashInterval
579  {
580  get;
581  set;
582  }
583 
584  [Serialize("0,0,0,0", IsPropertySaveable.Yes, description: "Color of lightning-like flashes of light in the level."), Editable]
585  public Color FlashColor
586  {
587  get;
588  set;
589  }
590 
591  [Serialize(120.0f, IsPropertySaveable.Yes, description: "How far the level walls' edge texture portrudes outside the actual, \"physical\" edge of the cell."), Editable(minValue: 0.0f, maxValue: 1000.0f)]
593  {
594  get;
595  private set;
596  }
597 
598  [Serialize(1000.0f, IsPropertySaveable.Yes, description: "How far inside the level walls the edge texture continues."), Editable(minValue: 0.0f, maxValue: 10000.0f)]
600  {
601  get;
602  private set;
603  }
604 
605  [Header("Colors")]
606  [Serialize("27,30,36", IsPropertySaveable.Yes), Editable]
607  public Color AmbientLightColor
608  {
609  get;
610  set;
611  }
612 
613  [Serialize("20,40,50", IsPropertySaveable.Yes), Editable]
615  {
616  get;
617  set;
618  }
619 
620  [Serialize("20,40,50", IsPropertySaveable.Yes), Editable]
621  public Color BackgroundColor
622  {
623  get;
624  set;
625  }
626 
627  [Serialize("255,255,255", IsPropertySaveable.Yes), Editable]
628  public Color WallColor
629  {
630  get;
631  set;
632  }
633 
634  [Serialize("255,255,255", IsPropertySaveable.Yes), Editable]
635  public Color WaterParticleColor
636  {
637  get;
638  set;
639  }
640 
641 
642  [Header("Sounds")]
643  [Serialize(false, IsPropertySaveable.Yes, description: "Should the \"ambient noise\" of the biome play in this level if it's an outpost level."), Editable]
645  {
646  get;
647  set;
648  }
649 
651  public float WaterAmbienceVolume
652  {
653  get;
654  set;
655  }
656 
657 
658  public Sprite BackgroundSprite { get; private set; }
659  public Sprite BackgroundTopSprite { get; private set; }
660  public Sprite WallSprite { get; private set; }
661  public Sprite WallEdgeSprite { get; private set; }
662  public Sprite DestructibleWallSprite { get; private set; }
663  public Sprite DestructibleWallEdgeSprite { get; private set; }
664  public Sprite WallSpriteDestroyed { get; private set; }
665  public Sprite WaterParticles { get; private set; }
666 
667 #if CLIENT
668  public Sounds.Sound FlashSound { get; private set; }
669 #endif
670 
671  #warning TODO: this should be in the unit test project (#3164)
672  public static void CheckValidity()
673 
674  {
675  foreach (Biome biome in Biome.Prefabs)
676  {
677  for (float i = 0.0f; i <= 100.0f; i += 0.5f)
678  {
679  if (GetRandom("test", LevelData.LevelType.LocationConnection, i, biome.Identifier) == null)
680  {
681  DebugConsole.ThrowError($"No suitable level generation parameters found for a specific type of level (level type: LocationConnection, difficulty: {i}, biome: {biome.Identifier})");
682  }
683  if (GetRandom("test", LevelData.LevelType.Outpost, i, biome.Identifier) == null)
684  {
685  DebugConsole.ThrowError($"No suitable level generation parameters found for a specific type of level (level type: Outpost, difficulty: {i}, biome: {biome.Identifier})");
686  }
687  }
688  }
689  }
690 
691  public static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, float difficulty, Identifier biomeId = default, bool pvpOnly = false, bool biomeTransition = false)
692  {
693  Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
694 
695  if (!LevelParams.Any())
696  {
697  throw new InvalidOperationException("Level generation presets not found - using default presets");
698  }
699 
700  var levelParamsOrdered = LevelParams.OrderBy(l => l.UintIdentifier);
701 
702  var matchingLevelParams = levelParamsOrdered.Where(lp =>
703  lp.Type == type &&
704  (lp.AnyBiomeAllowed || lp.AllowedBiomeIdentifiers.Any()) &&
705  !lp.AllowedBiomeIdentifiers.Contains("None".ToIdentifier()));
706 
707  if (biomeTransition)
708  {
709  var biomeTransitionParams = matchingLevelParams.Where(lp =>
710  lp.TransitionFromPreviousBiome && lp.AllowedBiomeIdentifiers.Contains(biomeId));
711 
712  if (biomeTransitionParams.Any())
713  {
714  return ToolBox.SelectWeightedRandom(biomeTransitionParams, p => p.Commonness, Rand.RandSync.ServerAndClient);
715  }
716  }
717  else
718  {
719  matchingLevelParams = matchingLevelParams.Where(lp => !lp.TransitionFromPreviousBiome);
720  }
721 
722  if (pvpOnly)
723  {
724  var pvpOnlyLevels = matchingLevelParams.Where(static lp => lp.IsPvPLevel);
725 
726  if (pvpOnlyLevels.Any())
727  {
728  matchingLevelParams = pvpOnlyLevels;
729  }
730  else
731  {
732  DebugConsole.AddWarning("No PvP specific level generation presets found - using all level generation presets instead.");
733  }
734  }
735  else
736  {
737  matchingLevelParams = matchingLevelParams.Where(static lp => !lp.IsPvPLevel);
738  }
739 
740  if (biomeId.IsEmpty || biomeId == "Random")
741  {
742  //we don't want end levels when generating a completely random level (e.g. in mission mode)
743  matchingLevelParams = matchingLevelParams.Where(lp => lp.AnyBiomeAllowed || !lp.AllowedBiomeIdentifiers.All(b => Biome.Prefabs[b].IsEndBiome));
744  }
745  else
746  {
747  bool isEndBiome = Biome.Prefabs.TryGet(biomeId, out Biome biome) && biome.IsEndBiome;
748  if (isEndBiome && matchingLevelParams.Any(lp => lp.AllowedBiomeIdentifiers.Contains(biomeId)))
749  {
750  //in the end biome, we must choose level parameters meant specifically for the end levels
751  matchingLevelParams = matchingLevelParams.Where(lp => lp.AllowedBiomeIdentifiers.Contains(biomeId));
752  }
753  else
754  {
755  matchingLevelParams = matchingLevelParams.Where(lp => lp.AnyBiomeAllowed || lp.AllowedBiomeIdentifiers.Contains(biomeId));
756  }
757  }
758 
759  if (!matchingLevelParams.Any())
760  {
761  DebugConsole.ThrowError($"Suitable level generation presets not found (biome \"{biomeId.IfEmpty("null".ToIdentifier())}\", type: \"{type}\")");
762  if (!biomeId.IsEmpty)
763  {
764  //try to find params that at least have a suitable type
765  matchingLevelParams = levelParamsOrdered.Where(lp => lp.Type == type);
766  if (!matchingLevelParams.Any())
767  {
768  //still not found, give up and choose some params randomly
769  matchingLevelParams = levelParamsOrdered;
770  }
771  }
772  }
773 
774  if (!matchingLevelParams.Any(lp => difficulty >= lp.MinLevelDifficulty && difficulty <= lp.MaxLevelDifficulty))
775  {
776  DebugConsole.ThrowError($"Suitable level generation presets not found (biome \"{biomeId.IfEmpty("null".ToIdentifier())}\", type: \"{type}\", difficulty: {difficulty})");
777  }
778  else
779  {
780  matchingLevelParams = matchingLevelParams.Where(lp => difficulty >= lp.MinLevelDifficulty && difficulty <= lp.MaxLevelDifficulty);
781  }
782 
783  return ToolBox.SelectWeightedRandom(matchingLevelParams, p => p.Commonness, Rand.RandSync.ServerAndClient);
784  }
785 
786  public LevelGenerationParams(ContentXElement element, LevelGenerationParametersFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName))
787  {
788  OldIdentifier = element.GetAttributeIdentifier("oldidentifier", Identifier.Empty);
790 
791  if (element is null) { throw new ArgumentNullException($"{nameof(element)} is null"); }
792 
793  var allowedBiomeIdentifiers = element.GetAttributeIdentifierArray("biomes", Array.Empty<Identifier>()).ToHashSet();
794  AnyBiomeAllowed = allowedBiomeIdentifiers.Contains("any".ToIdentifier());
795  allowedBiomeIdentifiers.Remove("any".ToIdentifier());
796  AllowedBiomeIdentifiers = allowedBiomeIdentifiers.ToImmutableHashSet();
797 
798  DisplayName = TextManager.Get($"levelname.{Identifier}");
799  Description = TextManager.Get($"leveldescription.{Identifier}");
800 
801  var nameIdentifier = element.GetAttributeIdentifier("nameidentifier", Identifier.Empty);
802  var descriptionIdentifier = element.GetAttributeIdentifier("descriptionidentifier", Identifier.Empty);
803  if (!nameIdentifier.IsEmpty)
804  {
805  DisplayName = TextManager.Get(nameIdentifier);
806  }
807  if (!descriptionIdentifier.IsEmpty)
808  {
809  Description = TextManager.Get(descriptionIdentifier);
810  }
811 
812  foreach (var subElement in element.Elements())
813  {
814  switch (subElement.Name.ToString().ToLowerInvariant())
815  {
816  case "background":
817  BackgroundSprite = new Sprite(subElement);
818  break;
819  case "backgroundtop":
820  BackgroundTopSprite = new Sprite(subElement);
821  break;
822  case "wall":
823  WallSprite = new Sprite(subElement);
824  break;
825  case "walledge":
826  WallEdgeSprite = new Sprite(subElement);
827  break;
828  case "destructiblewall":
829  DestructibleWallSprite = new Sprite(subElement);
830  break;
831  case "destructiblewalledge":
832  DestructibleWallEdgeSprite = new Sprite(subElement);
833  break;
834  case "walldestroyed":
835  WallSpriteDestroyed = new Sprite(subElement);
836  break;
837  case "waterparticles":
838  WaterParticles = new Sprite(subElement);
839  break;
840 #if CLIENT
841  case "flashsound":
842  FlashSound = GameMain.SoundManager.LoadSound(subElement);
843  break;
844 #endif
845  }
846  }
847  }
848 
849  public override void Dispose() { }
850  }
851 }
static readonly PrefabCollection< Biome > Prefabs
Definition: Biome.cs:10
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
Identifier GetAttributeIdentifier(string key, string def)
static Sounds.SoundManager SoundManager
Definition: GameMain.cs:80
static readonly PrefabCollection< LevelGenerationParams > LevelParams
static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, float difficulty, Identifier biomeId=default, bool pvpOnly=false, bool biomeTransition=false)
Dictionary< Identifier, SerializableProperty > SerializableProperties
LevelGenerationParams(ContentXElement element, LevelGenerationParametersFile file)
readonly ImmutableHashSet< Identifier > AllowedBiomeIdentifiers
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)