Client LuaCsForBarotrauma
LevelData.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Globalization;
6 using System.Linq;
7 using System.Xml.Linq;
9 
10 namespace Barotrauma
11 {
12  class LevelData
13  {
14  public enum LevelType
15  {
17  Outpost
18  }
19 
20  public readonly LevelType Type;
21 
22  public readonly string Seed;
23 
24  public readonly float Difficulty;
25 
26  public readonly Biome Biome;
27 
28  public LevelGenerationParams GenerationParams { get; private set; }
29 
30  public bool HasBeaconStation;
31  public bool IsBeaconActive;
32 
33  public bool HasHuntingGrounds, OriginallyHadHuntingGrounds;
34 
38  public const float HuntingGroundsDifficultyThreshold = 25;
39 
43  public const float MaxHuntingGroundsProbability = 0.3f;
44 
46 
48 
50 
52 
53  public enum ThalamusSpawn
54  {
55  Random,
56  Forced,
57  Disabled
58  }
59 
62  public static ThalamusSpawn ForceThalamus = ThalamusSpawn.Random;
63 
64  public bool AllowInvalidOutpost;
65 
66  public readonly Point Size;
67 
71  public readonly int InitialDepth;
72 
76  public int? MinMainPathWidth;
77 
81  public readonly List<Identifier> EventHistory = new List<Identifier>();
82 
86  public readonly List<Identifier> NonRepeatableEvents = new List<Identifier>();
87 
88  public readonly Dictionary<EventSet, int> FinishedEvents = new Dictionary<EventSet, int>();
89 
93  public bool EventsExhausted { get; set; }
94 
98  public float CrushDepth
99  {
100  get
101  {
102  return Math.Max(Size.Y, Level.DefaultRealWorldCrushDepth / Physics.DisplayToRealWorldRatio) - InitialDepth;
103  }
104  }
105 
109  public float RealWorldCrushDepth
110  {
111  get
112  {
113  return Math.Max(Size.Y * Physics.DisplayToRealWorldRatio, Level.DefaultRealWorldCrushDepth);
114  }
115  }
116 
120  public bool IsAllowedDifficulty(float minDifficulty, float maxDifficulty) => Difficulty >= minDifficulty && Difficulty <= maxDifficulty;
121 
122  public LevelData(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome)
123  {
124  Seed = seed ?? throw new ArgumentException("Seed was null");
125  Biome = biome ?? throw new ArgumentException("Biome was null");
126  GenerationParams = generationParams ?? throw new ArgumentException("Level generation parameters were null");
128  Difficulty = difficulty;
129 
130  sizeFactor = MathHelper.Clamp(sizeFactor, 0.0f, 1.0f);
131  int width = (int)MathHelper.Lerp(generationParams.MinWidth, generationParams.MaxWidth, sizeFactor);
132 
133  InitialDepth = (int)MathHelper.Lerp(generationParams.InitialDepthMin, generationParams.InitialDepthMax, sizeFactor);
134 
135  Size = new Point(
136  (int)MathUtils.Round(width, Level.GridCellSize),
137  (int)MathUtils.Round(generationParams.Height, Level.GridCellSize));
138  }
139 
140  public LevelData(XElement element, float? forceDifficulty = null, bool clampDifficultyToBiome = false)
141  {
142  Seed = element.GetAttributeString("seed", "");
143  Size = element.GetAttributePoint("size", new Point(1000));
144  Enum.TryParse(element.GetAttributeString("type", "LocationConnection"), out Type);
145 
146  HasBeaconStation = element.GetAttributeBool("hasbeaconstation", false);
147  IsBeaconActive = element.GetAttributeBool("isbeaconactive", false);
148 
149  HasHuntingGrounds = element.GetAttributeBool("hashuntinggrounds", false);
150  OriginallyHadHuntingGrounds = element.GetAttributeBool("originallyhadhuntinggrounds", HasHuntingGrounds);
151 
152  string generationParamsId = element.GetAttributeString("generationparams", "");
153  GenerationParams = LevelGenerationParams.LevelParams.Find(l => l.Identifier == generationParamsId || (!l.OldIdentifier.IsEmpty && l.OldIdentifier == generationParamsId));
154  if (GenerationParams == null)
155  {
156  DebugConsole.ThrowError($"Error while loading a level. Could not find level generation params with the ID \"{generationParamsId}\".");
157  GenerationParams = LevelGenerationParams.LevelParams.FirstOrDefault(l => l.Type == Type);
159  }
160 
161  InitialDepth = element.GetAttributeInt("initialdepth", GenerationParams.InitialDepthMin);
162 
163  string biomeIdentifier = element.GetAttributeString("biome", "");
164  Biome = Biome.Prefabs.FirstOrDefault(b => b.Identifier == biomeIdentifier || (!b.OldIdentifier.IsEmpty && b.OldIdentifier == biomeIdentifier));
165  if (Biome == null)
166  {
167  DebugConsole.ThrowError($"Error in level data: could not find the biome \"{biomeIdentifier}\".");
168  Biome = Biome.Prefabs.First();
169  }
170 
171  Difficulty = forceDifficulty ?? element.GetAttributeFloat("difficulty", 0.0f);
172  if (clampDifficultyToBiome)
173  {
175  }
176 
177  string[] prefabNames = element.GetAttributeStringArray("eventhistory", Array.Empty<string>());
178  EventHistory.AddRange(EventPrefab.Prefabs.Where(p => prefabNames.Any(n => p.Identifier == n)).Select(p => p.Identifier));
179 
180  string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", Array.Empty<string>());
181  NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n)).Select(p => p.Identifier));
182 
183  string finishedEventsName = nameof(FinishedEvents);
184  if (element.GetChildElement(finishedEventsName) is { } finishedEventsElement)
185  {
186  foreach (var childElement in finishedEventsElement.GetChildElements(finishedEventsName))
187  {
188  Identifier eventSetIdentifier = childElement.GetAttributeIdentifier("set", Identifier.Empty);
189  if (eventSetIdentifier.IsEmpty) { continue; }
190  if (!EventSet.Prefabs.TryGet(eventSetIdentifier, out EventSet eventSet))
191  {
192  foreach (var prefab in EventSet.Prefabs)
193  {
194  if (FindSetRecursive(prefab, eventSetIdentifier) is { } foundSet)
195  {
196  eventSet = foundSet;
197  break;
198  }
199  }
200  }
201  if (eventSet is null) { continue; }
202  int count = childElement.GetAttributeInt("count", 0);
203  if (count < 1) { continue; }
204  FinishedEvents.TryAdd(eventSet, count);
205  }
206 
207  static EventSet FindSetRecursive(EventSet parentSet, Identifier setIdentifier)
208  {
209  foreach (var childSet in parentSet.ChildSets)
210  {
211  if (childSet.Identifier == setIdentifier)
212  {
213  return childSet;
214  }
215  if (FindSetRecursive(childSet, setIdentifier) is { } foundSet)
216  {
217  return foundSet;
218  }
219  }
220  return null;
221  }
222  }
223 
224  EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false);
225  }
226 
230  public LevelData(LocationConnection locationConnection)
231  {
232  Seed = locationConnection.Locations[0].LevelData.Seed + locationConnection.Locations[1].LevelData.Seed;
233  bool connectionIsBiomeTransition = locationConnection.Locations[0].Biome.Identifier != locationConnection.Locations[1].Biome.Identifier;
234  Biome = locationConnection.Biome;
235  Type = LevelType.LocationConnection;
236  Difficulty = locationConnection.Difficulty;
237  GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.LocationConnection, Difficulty, Biome.Identifier, biomeTransition: connectionIsBiomeTransition);
238 
239  float sizeFactor = MathUtils.InverseLerp(
242  locationConnection.Length);
243  int width = (int)MathHelper.Lerp(GenerationParams.MinWidth, GenerationParams.MaxWidth, sizeFactor);
244  Size = new Point(
245  (int)MathUtils.Round(width, Level.GridCellSize),
246  (int)MathUtils.Round(GenerationParams.Height, Level.GridCellSize));
247 
248  var rand = new MTRandom(ToolBox.StringToInt(Seed));
249  InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
250  if (Biome.IsEndBiome)
251  {
252  HasHuntingGrounds = false;
253  HasBeaconStation = false;
254  }
255  else
256  {
257  HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(HuntingGroundsDifficultyThreshold, 100.0f, Difficulty) * MaxHuntingGroundsProbability;
258  HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
259  }
260  IsBeaconActive = false;
261  }
262 
266  public LevelData(Location location, Map map, float difficulty)
267  {
268  Seed = location.NameIdentifier.Value + map.Locations.IndexOf(location);
269  Biome = location.Biome;
270  Type = LevelType.Outpost;
271  Difficulty = difficulty;
273 
274  var rand = new MTRandom(ToolBox.StringToInt(Seed));
275  int width = (int)MathHelper.Lerp(GenerationParams.MinWidth, GenerationParams.MaxWidth, (float)rand.NextDouble());
276  InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
277  Size = new Point(
278  (int)MathUtils.Round(width, Level.GridCellSize),
279  (int)MathUtils.Round(GenerationParams.Height, Level.GridCellSize));
280  }
281 
282  public static LevelData CreateRandom(string seed = "", float? difficulty = null, LevelGenerationParams generationParams = null, Identifier biomeId = default, bool requireOutpost = false, bool pvpOnly = false)
283  {
284  if (string.IsNullOrEmpty(seed))
285  {
286  seed = Rand.Range(0, int.MaxValue, Rand.RandSync.ServerAndClient).ToString();
287  }
288 
289  Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
290 
291  LevelType type = generationParams?.Type ??
292  (requireOutpost
293  ? LevelType.Outpost
294  : LevelType.LocationConnection);
295 
296  float selectedDifficulty = difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.ServerAndClient);
297 
298  Biome biome = null;
299  if (!biomeId.IsEmpty && biomeId != "Random")
300  {
301  Biome.Prefabs.TryGet(biomeId, out biome);
302  }
303  generationParams ??= LevelGenerationParams.GetRandom(seed, type, selectedDifficulty, pvpOnly: pvpOnly, biomeId: biomeId);
304 
305  biome ??=
306  Biome.Prefabs.FirstOrDefault(b => generationParams?.AllowedBiomeIdentifiers.Contains(b.Identifier) ?? false) ??
307  Biome.Prefabs.GetRandom(Rand.RandSync.ServerAndClient);
308 
309  var levelData = new LevelData(
310  seed,
311  selectedDifficulty,
312  Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient),
313  generationParams,
314  biome);
315  if (type == LevelType.LocationConnection)
316  {
317  float beaconRng = Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient);
318  levelData.HasBeaconStation = beaconRng < 0.5f;
319  levelData.IsBeaconActive = beaconRng > 0.25f;
320  }
321  if (GameMain.GameSession?.GameMode != null)
322  {
323  foreach (Mission mission in GameMain.GameSession.GameMode.Missions)
324  {
325  mission.AdjustLevelData(levelData);
326  }
327  }
328  return levelData;
329  }
330 
331  public void ReassignGenerationParams(string seed)
332  {
334  }
336 
337  public static IEnumerable<OutpostGenerationParams> GetSuitableOutpostGenerationParams(Location location, LevelData levelData)
338  {
339  var suitableParams = OutpostGenerationParams.OutpostParams
340  .Where(p => p.LevelType == null || levelData.Type == p.LevelType)
341  .Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier));
342  if (!suitableParams.Any())
343  {
344  suitableParams = OutpostGenerationParams.OutpostParams
345  .Where(p => p.LevelType == null || levelData.Type == p.LevelType)
346  .Where(p => location == null || !p.AllowedLocationTypes.Any());
347  if (!suitableParams.Any())
348  {
349  DebugConsole.ThrowError($"No suitable outpost generation parameters found for the location type \"{location.Type.Identifier}\". Selecting random parameters.");
350  suitableParams = OutpostGenerationParams.OutpostParams;
351  }
352  }
353  return suitableParams;
354  }
355 
356  public void Save(XElement parentElement)
357  {
358  var newElement = new XElement("Level",
359  new XAttribute("seed", Seed),
360  new XAttribute("biome", Biome.Identifier),
361  new XAttribute("type", Type.ToString()),
362  new XAttribute("difficulty", Difficulty.ToString("G", CultureInfo.InvariantCulture)),
363  new XAttribute("size", XMLExtensions.PointToString(Size)),
364  new XAttribute("generationparams", GenerationParams.Identifier),
365  new XAttribute("initialdepth", InitialDepth),
366  new XAttribute(nameof(EventsExhausted).ToLower(), EventsExhausted));
367 
368  if (HasBeaconStation)
369  {
370  newElement.Add(
371  new XAttribute("hasbeaconstation", HasBeaconStation.ToString()),
372  new XAttribute("isbeaconactive", IsBeaconActive.ToString()));
373  }
374 
375  if (HasHuntingGrounds)
376  {
377  newElement.Add(
378  new XAttribute("hashuntinggrounds", true));
379  }
380  if (HasHuntingGrounds || OriginallyHadHuntingGrounds)
381  {
382  newElement.Add(
383  new XAttribute("originallyhadhuntinggrounds", true));
384  }
385 
386  if (Type == LevelType.Outpost)
387  {
388  if (EventHistory.Any())
389  {
390  newElement.Add(new XAttribute("eventhistory", string.Join(',', EventHistory)));
391  }
392  if (NonRepeatableEvents.Any())
393  {
394  newElement.Add(new XAttribute("nonrepeatableevents", string.Join(',', NonRepeatableEvents)));
395  }
396  if (FinishedEvents.Any())
397  {
398  var finishedEventsElement = new XElement(nameof(FinishedEvents));
399  foreach (var (set, count) in FinishedEvents)
400  {
401  var element = new XElement(nameof(FinishedEvents),
402  new XAttribute("set", set.Identifier),
403  new XAttribute("count", count));
404  finishedEventsElement.Add(element);
405  }
406  newElement.Add(finishedEventsElement);
407  }
408  }
409 
410  parentElement.Add(newElement);
411  }
412  }
413 }
readonly bool IsEndBiome
Definition: Biome.cs:16
static readonly PrefabCollection< Biome > Prefabs
Definition: Biome.cs:10
readonly float MinDifficulty
Definition: Biome.cs:19
float AdjustedMaxDifficulty
Definition: Biome.cs:22
static readonly PrefabCollection< EventPrefab > Prefabs
Definition: EventPrefab.cs:11
Event sets are sets of random events that occur within a level (most commonly, monster spawns and scr...
Definition: EventSet.cs:31
static readonly PrefabCollection< EventSet > Prefabs
Definition: EventSet.cs:44
readonly ImmutableArray< EventSet > ChildSets
Definition: EventSet.cs:312
static GameSession?? GameSession
Definition: GameMain.cs:88
int? MinMainPathWidth
Determined during level generation based on the size of the submarine. Null if the level hasn't been ...
Definition: LevelData.cs:76
const float MaxHuntingGroundsProbability
Probability of hunting grounds appearing in 100% difficulty levels.
Definition: LevelData.cs:43
bool EventsExhausted
'Exhaustible' sets won't appear in the same level until after one world step (~10 min,...
Definition: LevelData.cs:93
SubmarineInfo ForceBeaconStation
Definition: LevelData.cs:47
static LevelData CreateRandom(string seed="", float? difficulty=null, LevelGenerationParams generationParams=null, Identifier biomeId=default, bool requireOutpost=false, bool pvpOnly=false)
Definition: LevelData.cs:282
float CrushDepth
The crush depth of a non-upgraded submarine in in-game coordinates. Note that this can be above the t...
Definition: LevelData.cs:99
OutpostGenerationParams ForceOutpostGenerationParams
Definition: LevelData.cs:45
LevelData(XElement element, float? forceDifficulty=null, bool clampDifficultyToBiome=false)
Definition: LevelData.cs:140
static SubmarineInfo ConsoleForceWreck
Definition: LevelData.cs:60
LevelGenerationParams GenerationParams
Definition: LevelData.cs:28
float RealWorldCrushDepth
The crush depth of a non-upgraded submarine in "real world units" (meters from the surface of Europa)...
Definition: LevelData.cs:110
RuinGenerationParams ForceRuinGenerationParams
Definition: LevelData.cs:51
readonly Dictionary< EventSet, int > FinishedEvents
Definition: LevelData.cs:88
readonly List< Identifier > EventHistory
Events that have previously triggered in this level. Used for making events the player hasn't seen ye...
Definition: LevelData.cs:81
readonly Point Size
Definition: LevelData.cs:66
readonly float Difficulty
Definition: LevelData.cs:24
LevelData(LocationConnection locationConnection)
Instantiates level data using the properties of the connection (seed, size, difficulty)
Definition: LevelData.cs:230
bool IsAllowedDifficulty(float minDifficulty, float maxDifficulty)
Inclusive (matching the min an max values is accepted).
readonly LevelType Type
Definition: LevelData.cs:20
readonly int InitialDepth
The depth at which the level starts at, in in-game coordinates. E.g. if this was set to 100 000 (= 10...
Definition: LevelData.cs:71
LevelData(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome)
Definition: LevelData.cs:122
static ThalamusSpawn ForceThalamus
Definition: LevelData.cs:62
void Save(XElement parentElement)
Definition: LevelData.cs:356
SubmarineInfo ForceWreck
Definition: LevelData.cs:49
void ReassignGenerationParams(string seed)
Definition: LevelData.cs:331
readonly List< Identifier > NonRepeatableEvents
Events that have already triggered in this level and can never trigger again. EventSet....
Definition: LevelData.cs:86
const float HuntingGroundsDifficultyThreshold
Minimum difficulty of the level before hunting grounds can appear.
Definition: LevelData.cs:38
readonly string Seed
Definition: LevelData.cs:22
static IEnumerable< OutpostGenerationParams > GetSuitableOutpostGenerationParams(Location location, LevelData levelData)
Definition: LevelData.cs:337
readonly Biome Biome
Definition: LevelData.cs:26
LevelData(Location location, Map map, float difficulty)
Instantiates level data using the properties of the location
Definition: LevelData.cs:266
bool OutpostGenerationParamsExist
Definition: LevelData.cs:335
static SubmarineInfo ConsoleForceBeaconStation
Definition: LevelData.cs:61
static readonly PrefabCollection< LevelGenerationParams > LevelParams
static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, float difficulty, Identifier biomeId=default, bool pvpOnly=false, bool biomeTransition=false)
LocationType Type
Definition: Location.cs:94
Identifier NameIdentifier
Definition: Location.cs:61
LevelData LevelData
Definition: Location.cs:98
Mersenne Twister based random
Definition: MTRandom.cs:9
static MapGenerationParams Instance
static readonly PrefabCollection< OutpostGenerationParams > OutpostParams
readonly Identifier Identifier
Definition: Prefab.cs:34