2 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
21 private int minWidth, maxWidth, height;
23 private Point voronoiSiteInterval;
26 private Point voronoiSiteVariance;
30 private Point mainPathNodeIntervalRange;
32 private int caveCount;
37 private float bottomHoleProbability;
40 private int seaFloorBaseDepth;
42 private int seaFloorVariance;
44 private int cellSubdivisionLength;
45 private float cellRoundingAmount;
46 private float cellIrregularity;
48 private int mountainCountMin, mountainCountMax;
50 private int mountainHeightMin, mountainHeightMax;
52 private float waterParticleScale;
54 private int initialDepthMin, initialDepthMax;
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)]
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)]
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)]
101 private Vector2 startPosition;
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)]
107 get {
return startPosition; }
110 startPosition =
new Vector2(
111 MathHelper.Clamp(value.X, 0.0f, 1.0f),
112 MathHelper.Clamp(value.Y, 0.0f, 1.0f));
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)]
120 get {
return endPosition; }
123 endPosition =
new Vector2(
124 MathHelper.Clamp(value.X, 0.0f, 1.0f),
125 MathHelper.Clamp(value.Y, 0.0f, 1.0f));
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)]
133 get {
return forceOutpostPosition; }
136 forceOutpostPosition =
new Vector2(
137 MathHelper.Clamp(value.X, 0.0f, 1.0f),
138 MathHelper.Clamp(value.Y, 0.0f, 1.0f));
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]
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()]
152 get {
return bottomHoleProbability; }
153 set { bottomHoleProbability = MathHelper.Clamp(value, 0.0f, 1.0f); }
159 get {
return minWidth; }
160 set { minWidth = MathHelper.Clamp(value, 2000, 1000000); }
166 get {
return maxWidth; }
167 set { maxWidth = MathHelper.Clamp(value, 2000, 1000000); }
173 get {
return height; }
174 set { height = MathHelper.Clamp(value, 2000, 1000000); }
177 [
Serialize(80000,
IsPropertySaveable.Yes, description:
"Minimum depth at the top of the level (100 corresponds to 1 meter)."),
Editable(MinValueInt = 0, MaxValueInt = 1000000)]
180 get {
return initialDepthMin; }
181 set { initialDepthMin = Math.Max(value, 0); }
184 [
Serialize(80000,
IsPropertySaveable.Yes, description:
"Maximum depth at the top of the level (100 corresponds to 1 meter)."),
Editable(MinValueInt = 0, MaxValueInt = 1000000)]
187 get {
return initialDepthMax; }
188 set { initialDepthMax = Math.Max(value, initialDepthMin); }
191 [
Header(
"Level geometry")]
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]
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)")]
205 get {
return voronoiSiteInterval; }
208 voronoiSiteInterval.X = MathHelper.Clamp(value.X, 100,
MinWidth / 2);
209 voronoiSiteInterval.Y = MathHelper.Clamp(value.Y, 100, height / 2);
214 "Small values produce roughly rectangular level walls. The larger the values are, the less uniform the shapes get.")]
217 get {
return voronoiSiteVariance; }
220 voronoiSiteVariance =
new Point(
221 MathHelper.Clamp(value.X, 0, voronoiSiteInterval.X),
222 MathHelper.Clamp(value.Y, 0, voronoiSiteInterval.Y));
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.")]
231 get {
return cellSubdivisionLength; }
234 cellSubdivisionLength = Math.Max(value, 10);
240 +
"Note that the final shape of the cells is also affected by the CellSubdivisionLength parameter.")]
243 get {
return cellRoundingAmount; }
246 cellRoundingAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
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.")]
254 get {
return cellIrregularity; }
257 cellIrregularity = MathHelper.Clamp(value, 0.0f, 1.0f);
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)]
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)]
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]
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.")]
297 get {
return mainPathNodeIntervalRange; }
300 mainPathNodeIntervalRange.X = MathHelper.Clamp(value.X, 100,
MinWidth / 2);
301 mainPathNodeIntervalRange.Y = MathHelper.Clamp(value.Y, mainPathNodeIntervalRange.X,
MinWidth / 2);
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)]
313 [
Serialize(1000,
IsPropertySaveable.Yes, description:
"The total number of level objects (vegetation, vents, etc) in the level."),
Editable(MinValueInt = 0, MaxValueInt = 100000)]
331 get {
return caveCount; }
332 set { caveCount = MathHelper.Clamp(value, 0, 100); }
349 [
Serialize(
"9600,19200",
IsPropertySaveable.Yes, description:
"The minimum and maximum distance between two resource spawn points on a cave path."),
Editable(100, 100000)]
357 "In addition to this, resource commonness affects the cluster size. Less common resources spawn in smaller clusters."),
Editable(1, 20)]
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)]
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)]
377 [
Serialize(0,
IsPropertySaveable.Yes, description:
"Number of islands (static wall chunks along the main path) in the level."),
Editable(MinValueInt = 0, MaxValueInt = 100)]
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()]
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)]
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)]
445 get {
return seaFloorBaseDepth; }
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)]
452 get {
return seaFloorVariance; }
453 set { seaFloorVariance = value; }
459 get {
return mountainCountMin; }
462 mountainCountMin = Math.Max(value, 0);
469 get {
return mountainCountMax; }
472 mountainCountMax = Math.Max(value, 0);
479 get {
return mountainHeightMin; }
482 mountainHeightMin = Math.Max(value, 0);
489 get {
return mountainHeightMax; }
492 mountainHeightMax = Math.Max(value, 0);
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)]
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)]
514 #region Wreck parameters
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)]
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)]
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)]
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)]
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)]
551 get {
return waterParticleScale; }
552 private set { waterParticleScale = Math.Max(value, 0.01f); }
555 private Vector2 waterParticleVelocity;
559 get {
return waterParticleVelocity; }
560 private set { waterParticleVelocity = value; }
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)]
671 #warning TODO: this should be in the unit test project (#3164)
677 for (
float i = 0.0f; i <= 100.0f; i += 0.5f)
681 DebugConsole.ThrowError($
"No suitable level generation parameters found for a specific type of level (level type: LocationConnection, difficulty: {i}, biome: {biome.Identifier})");
685 DebugConsole.ThrowError($
"No suitable level generation parameters found for a specific type of level (level type: Outpost, difficulty: {i}, biome: {biome.Identifier})");
693 Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
697 throw new InvalidOperationException(
"Level generation presets not found - using default presets");
700 var levelParamsOrdered =
LevelParams.OrderBy(l => l.UintIdentifier);
702 var matchingLevelParams = levelParamsOrdered.Where(lp =>
704 (lp.AnyBiomeAllowed || lp.AllowedBiomeIdentifiers.Any()) &&
705 !lp.AllowedBiomeIdentifiers.Contains(
"None".ToIdentifier()));
709 var biomeTransitionParams = matchingLevelParams.Where(lp =>
710 lp.TransitionFromPreviousBiome && lp.AllowedBiomeIdentifiers.Contains(biomeId));
712 if (biomeTransitionParams.Any())
714 return ToolBox.SelectWeightedRandom(biomeTransitionParams, p => p.Commonness, Rand.RandSync.ServerAndClient);
719 matchingLevelParams = matchingLevelParams.Where(lp => !lp.TransitionFromPreviousBiome);
724 var pvpOnlyLevels = matchingLevelParams.Where(
static lp => lp.IsPvPLevel);
726 if (pvpOnlyLevels.Any())
728 matchingLevelParams = pvpOnlyLevels;
732 DebugConsole.AddWarning(
"No PvP specific level generation presets found - using all level generation presets instead.");
737 matchingLevelParams = matchingLevelParams.Where(
static lp => !lp.IsPvPLevel);
740 if (biomeId.IsEmpty || biomeId ==
"Random")
743 matchingLevelParams = matchingLevelParams.Where(lp => lp.AnyBiomeAllowed || !lp.AllowedBiomeIdentifiers.All(b =>
Biome.
Prefabs[b].IsEndBiome));
747 bool isEndBiome =
Biome.
Prefabs.TryGet(biomeId, out
Biome biome) && biome.IsEndBiome;
748 if (isEndBiome && matchingLevelParams.Any(lp => lp.AllowedBiomeIdentifiers.Contains(biomeId)))
751 matchingLevelParams = matchingLevelParams.Where(lp => lp.AllowedBiomeIdentifiers.Contains(biomeId));
755 matchingLevelParams = matchingLevelParams.Where(lp => lp.AnyBiomeAllowed || lp.AllowedBiomeIdentifiers.Contains(biomeId));
759 if (!matchingLevelParams.Any())
761 DebugConsole.ThrowError($
"Suitable level generation presets not found (biome \"{biomeId.IfEmpty("null".ToIdentifier())}\", type: \"{type}\")");
762 if (!biomeId.IsEmpty)
765 matchingLevelParams = levelParamsOrdered.Where(lp => lp.Type == type);
766 if (!matchingLevelParams.Any())
769 matchingLevelParams = levelParamsOrdered;
774 if (!matchingLevelParams.Any(lp => difficulty >= lp.MinLevelDifficulty && difficulty <= lp.MaxLevelDifficulty))
776 DebugConsole.ThrowError($
"Suitable level generation presets not found (biome \"{biomeId.IfEmpty("null".ToIdentifier())}\", type: \"{type}\", difficulty: {difficulty})");
780 matchingLevelParams = matchingLevelParams.Where(lp => difficulty >= lp.MinLevelDifficulty && difficulty <= lp.MaxLevelDifficulty);
783 return ToolBox.SelectWeightedRandom(matchingLevelParams, p => p.Commonness, Rand.RandSync.ServerAndClient);
791 if (element is
null) {
throw new ArgumentNullException($
"{nameof(element)} is null"); }
794 AnyBiomeAllowed = allowedBiomeIdentifiers.Contains(
"any".ToIdentifier());
795 allowedBiomeIdentifiers.Remove(
"any".ToIdentifier());
798 DisplayName = TextManager.Get($
"levelname.{Identifier}");
799 Description = TextManager.Get($
"leveldescription.{Identifier}");
803 if (!nameIdentifier.IsEmpty)
807 if (!descriptionIdentifier.IsEmpty)
809 Description = TextManager.Get(descriptionIdentifier);
812 foreach (var subElement
in element.Elements())
814 switch (subElement.Name.ToString().ToLowerInvariant())
819 case "backgroundtop":
828 case "destructiblewall":
831 case "destructiblewalledge":
834 case "walldestroyed":
837 case "waterparticles":
static readonly PrefabCollection< Biome > Prefabs
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
Identifier GetAttributeIdentifier(string key, string def)
static Sounds.SoundManager SoundManager
float ResourceSpawnChance
static readonly PrefabCollection< LevelGenerationParams > LevelParams
Sprite DestructibleWallSprite
float ThalamusProbability
float RuinSpawnProbability
float CaveResourceSpawnChance
float WreckFloodingHullMinWaterPercentage
Vector2 WaterParticleVelocity
float WallEdgeTextureWidth
float WaterAmbienceVolume
static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, float difficulty, Identifier biomeId=default, bool pvpOnly=false, bool biomeTransition=false)
float WreckFloodingHullMaxWaterPercentage
float WallEdgeExpandInwardsAmount
Dictionary< Identifier, SerializableProperty > SerializableProperties
LevelGenerationParams(ContentXElement element, LevelGenerationParametersFile file)
int FloatingIceChunkCount
float WreckHullFloodingChance
int AbyssResourceClustersMax
Point ResourceClusterSizeRange
Sprite BackgroundTopSprite
readonly ImmutableHashSet< Identifier > AllowedBiomeIdentifiers
Point MinSideTunnelRadius
static void CheckValidity()
LocalizedString DisplayName
Sprite DestructibleWallEdgeSprite
bool TransitionFromPreviousBiome
Point CaveResourceIntervalRange
LocalizedString Description
Point MainPathNodeIntervalRange
bool PlayNoiseLoopInOutpostLevel
int BackgroundCreatureAmount
int AbyssResourceClustersMin
string ForceBeaconStation
float AbyssIslandCaveProbability
Sprite WallSpriteDestroyed
readonly bool AnyBiomeAllowed
float WallEdgeExpandOutwardsAmount
float BottomHoleProbability
Color BackgroundTextureColor
Point ResourceIntervalRange
Point VoronoiSiteInterval
int CellSubdivisionLength
bool UseRandomRuinCount()
Point VoronoiSiteVariance
Vector2 ForceOutpostPosition
readonly Identifier Identifier
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)