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