Client LuaCsForBarotrauma
OutpostGenerationParams.cs
2 using System;
3 using System.Collections;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Linq;
7 
8 namespace Barotrauma
9 {
11  {
13 
14  public virtual string Name { get; private set; }
15 
16  private readonly HashSet<Identifier> allowedLocationTypes = new HashSet<Identifier>();
17 
21  public IEnumerable<Identifier> AllowedLocationTypes
22  {
23  get { return allowedLocationTypes; }
24  }
25 
26 
27  [Serialize(-1, IsPropertySaveable.Yes, description: "Should this type of outpost be forced to the locations at the end of the campaign map? 0 = first end level, 1 = second end level, and so on."), Editable(MinValueInt = -1, MaxValueInt = 10)]
29  {
30  get;
31  set;
32  }
33 
34  [Serialize(-1, IsPropertySaveable.Yes, description: "The closer to the current level difficulty this value is, the higher the probability of choosing these generation params are. Defaults to -1, which means we use the current difficulty."), Editable(MinValueInt = 1, MaxValueInt = 50)]
36  {
37  get;
38  set;
39  }
40 
41  [Serialize(10, IsPropertySaveable.Yes, description: "Total number of modules in the outpost."), Editable(MinValueInt = 1, MaxValueInt = 50)]
42  public int TotalModuleCount
43  {
44  get;
45  set;
46  }
47 
48  [Serialize(true, IsPropertySaveable.Yes, description: "Should the generator append generic (module flag \"none\") modules to the outpost to reach the total module count."), Editable]
50  {
51  get;
52  set;
53  }
54 
55  [Serialize(200.0f, IsPropertySaveable.Yes, description: "Minimum length of the hallways between modules. If 0, the generator will place the modules directly against each other assuming it can be done without making any modules overlap."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
56  public float MinHallwayLength
57  {
58  get;
59  set;
60  }
61 
62  [Serialize(false, IsPropertySaveable.Yes, description: "Should this outpost always be destructible, regardless if damaging outposts is allowed by the server?"), Editable]
63  public bool AlwaysDestructible
64  {
65  get;
66  set;
67  }
68 
69  [Serialize(false, IsPropertySaveable.Yes, description: "Should this outpost always be rewireable, regardless if rewiring is allowed by the server?"), Editable]
70  public bool AlwaysRewireable
71  {
72  get;
73  set;
74  }
75 
76  [Serialize(false, IsPropertySaveable.Yes, description: "Should stealing from this outpost be always allowed?"), Editable]
77  public bool AllowStealing
78  {
79  get;
80  set;
81  }
82 
83  [Serialize(true, IsPropertySaveable.Yes, description: "Should the crew spawn inside the outpost (if not, they'll spawn in the submarine)."), Editable]
85  {
86  get;
87  set;
88  }
89 
90  [Serialize(true, IsPropertySaveable.Yes, description: "Should doors at the edges of an outpost module that didn't get connected to another module be locked?"), Editable]
91  public bool LockUnusedDoors
92  {
93  get;
94  set;
95  }
96 
97  [Serialize(true, IsPropertySaveable.Yes, description: "Should gaps at the edges of an outpost module that didn't get connected to another module be removed?"), Editable]
98  public bool RemoveUnusedGaps
99  {
100  get;
101  set;
102  }
103 
104  [Serialize(false, IsPropertySaveable.Yes, description: "Should the whole outpost render behind submarines? Only set this to true if the submarine is intended to go inside the outpost."), Editable]
105  public bool DrawBehindSubs
106  {
107  get;
108  set;
109  }
110 
111  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Minimum amount of water in the hulls of the outpost."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
112  public float MinWaterPercentage
113  {
114  get;
115  set;
116  }
117 
118  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Maximum amount of water in the hulls of the outpost."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
119  public float MaxWaterPercentage
120  {
121  get;
122  set;
123  }
124 
126  {
127  get;
128  set;
129  }
130 
131  [Serialize("", IsPropertySaveable.Yes, description: "Identifier of the outpost generation parameters that should be used if this outpost has become critically irradiated."), Editable]
132  public string ReplaceInRadiation { get; set; }
133 
134  [Serialize(false, IsPropertySaveable.Yes, description: "By default, sonar only shows the outline of the sub/outpost from the outside. Enable this if you want to see each structure individually."), Editable]
136  {
137  get;
138  set;
139  }
140 
141  public ContentPath OutpostFilePath { get; set; }
142 
143  [Serialize("", IsPropertySaveable.Yes, description: "If set, a fully pre-built outpost with this tag will be used instead of generating the outpost."), Editable]
144  public Identifier OutpostTag { get; set; }
145 
147  {
149 
151  public int Count { get; set; }
152 
153  [Serialize(0, IsPropertySaveable.Yes, description: "Can be used to enforce the modules to be placed in a specific order, starting from the docking module (0 = first, 1 = second, etc)."), Editable]
154  public int Order { get; set; }
155 
156  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Minimum difficulty of the current level for the module to appear in the outpost."), Editable]
157  public float MinDifficulty { get; set; }
158 
159  [Serialize(100.0f, IsPropertySaveable.Yes, description: "Maximum difficulty of the current level for the module to appear in the outpost."), Editable]
160  public float MaxDifficulty { get; set; }
161 
162  [Serialize(1.0f, IsPropertySaveable.Yes, description: "Probability for this type of module to be included in the outpost."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
163  public float Probability { get; set; }
164 
166  public Identifier RequiredFaction { get; set; }
167 
168  public string Name => Identifier.Value;
169 
170  public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; set; }
171 
172  public ModuleCount(ContentXElement element)
173  {
174  Identifier = element.GetAttributeIdentifier("flag", element.GetAttributeIdentifier("moduletype", ""));
176  }
177 
178  public ModuleCount(Identifier id, int count)
179  {
181  Identifier = id;
182  Count = count;
183  RequiredFaction = Identifier.Empty;
184  }
185  }
186 
187  private readonly List<ModuleCount> moduleCounts = new List<ModuleCount>();
188 
189  public IReadOnlyList<ModuleCount> ModuleCounts
190  {
191  get { return moduleCounts; }
192  }
193 
194  private class NpcCollection : IReadOnlyList<HumanPrefab>
195  {
196  private class Entry
197  {
198  private readonly HumanPrefab humanPrefab = null;
199  private readonly Identifier setIdentifier = Identifier.Empty;
200  private readonly Identifier npcIdentifier = Identifier.Empty;
201 
202  public readonly Identifier FactionIdentifier = Identifier.Empty;
203 
204  public Entry(HumanPrefab humanPrefab, Identifier factionIdentifier)
205  {
206  this.humanPrefab = humanPrefab;
207  this.FactionIdentifier = factionIdentifier;
208  }
209 
210  public Entry(Identifier setIdentifier, Identifier npcIdentifier, Identifier factionIdentifier)
211  {
212  this.setIdentifier = setIdentifier;
213  this.npcIdentifier = npcIdentifier;
214  this.FactionIdentifier = factionIdentifier;
215  }
216 
217  public HumanPrefab HumanPrefab
218  => humanPrefab ?? NPCSet.Get(setIdentifier, npcIdentifier);
219  }
220 
221  private readonly List<Entry> entries = new List<Entry>();
222 
223  public void Add(HumanPrefab humanPrefab, Identifier factionIdentifier)
224  => entries.Add(new Entry(humanPrefab, factionIdentifier));
225 
226 
227  public void Add(Identifier setIdentifier, Identifier npcIdentifier, Identifier factionIdentifier)
228  => entries.Add(new Entry(setIdentifier, npcIdentifier, factionIdentifier));
229 
230  public IEnumerator<HumanPrefab> GetEnumerator()
231  {
232  foreach (var entry in entries)
233  {
234  if (entry == null) { continue; }
235  yield return entry.HumanPrefab;
236  }
237  }
238 
239  IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
240 
241  public IEnumerable<HumanPrefab> GetByFaction(IEnumerable<FactionPrefab> factions)
242  {
243  foreach (var entry in entries)
244  {
245  if (entry.FactionIdentifier == Identifier.Empty || factions.Any(f => f.Identifier == entry.FactionIdentifier))
246  {
247  yield return entry.HumanPrefab;
248  }
249  }
250  }
251 
252  public int Count => entries.Count;
253 
254  public HumanPrefab this[int index] => entries[index].HumanPrefab;
255  }
256 
257  private readonly ImmutableArray<NpcCollection> humanPrefabCollections;
258 
259  public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
260 
261  private ImmutableHashSet<Identifier> StoreIdentifiers { get; set; }
262 
263  #warning TODO: this shouldn't really accept any ContentFile, issue is that RuinConfigFile and OutpostConfigFile are separate derived classes
264  public OutpostGenerationParams(ContentXElement element, ContentFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
265  {
266  Name = element.GetAttributeString("name", Identifier.Value);
267  allowedLocationTypes = element.GetAttributeIdentifierArray("allowedlocationtypes", Array.Empty<Identifier>()).ToHashSet();
269 
270  if (element.GetAttribute("leveltype") != null)
271  {
272  string levelTypeStr = element.GetAttributeString("leveltype", "");
273  if (Enum.TryParse(levelTypeStr, out LevelData.LevelType parsedLevelType))
274  {
275  LevelType = parsedLevelType;
276  }
277  else
278  {
279  DebugConsole.ThrowError($"Error in outpost generation parameters \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.", contentPackage: element.ContentPackage);
280  }
281  }
282 
284  OutpostTag = element.GetAttributeIdentifier(nameof(OutpostTag), Identifier.Empty);
285 
286  var humanPrefabCollections = new List<NpcCollection>();
287  foreach (var subElement in element.Elements())
288  {
289  switch (subElement.Name.ToString().ToLowerInvariant())
290  {
291  case "modulecount":
292  var newModuleCount = new ModuleCount(subElement);
293  if (moduleCounts.None() && newModuleCount.Probability < 1.0f)
294  {
295  DebugConsole.AddWarning(
296  $"Potential error in outpost generation parameters \"{Identifier}\"." +
297  $" The first module is set to spawn with a probability of {newModuleCount.Probability}%. The first module must always spawn, so the probability will be ignored.",
298  contentPackage: ContentPackage);
299  newModuleCount.Probability = 1.0f;
300  }
301  else if (newModuleCount.Probability <= 0.0f)
302  {
303  DebugConsole.AddWarning(
304  $"Potential error in outpost generation parameters \"{Identifier}\"." +
305  $" Probability of the module {newModuleCount.Identifier} is 0% (the module should never spawn, so there's no reason to include it in the generation parameters.",
306  contentPackage: ContentPackage);
307  }
308  moduleCounts.Add(newModuleCount);
309  break;
310  case "npcs":
311  var newCollection = new NpcCollection();
312  foreach (var npcElement in subElement.Elements())
313  {
314  Identifier from = npcElement.GetAttributeIdentifier("from", Identifier.Empty);
315  Identifier faction = npcElement.GetAttributeIdentifier("faction", Identifier.Empty);
316  if (from != Identifier.Empty)
317  {
318  newCollection.Add(from, npcElement.GetAttributeIdentifier("identifier", Identifier.Empty), faction);
319  }
320  else
321  {
322  newCollection.Add(new HumanPrefab(npcElement, file, npcSetIdentifier: from), faction);
323  }
324  }
325  humanPrefabCollections.Add(newCollection);
326  break;
327  }
328  }
329 
330  this.humanPrefabCollections = humanPrefabCollections.ToImmutableArray();
331  }
332 
333  public int GetModuleCount(Identifier moduleFlag)
334  {
335  if (moduleFlag == Identifier.Empty || moduleFlag == "none") { return int.MaxValue; }
336  return moduleCounts.FirstOrDefault(m => m.Identifier == moduleFlag)?.Count ?? 0;
337  }
338 
339  public void SetModuleCount(Identifier moduleFlag, int count, float? probability = null, float? minDifficulty = null, float? maxDifficulty = null)
340  {
341  if (moduleFlag == Identifier.Empty || moduleFlag == "none") { return; }
342  if (count <= 0)
343  {
344  moduleCounts.RemoveAll(m => m.Identifier == moduleFlag);
345  }
346  else
347  {
348  var moduleCount = moduleCounts.FirstOrDefault(m => m.Identifier == moduleFlag);
349  if (moduleCount == null)
350  {
351  moduleCount = new ModuleCount(moduleFlag, count);
352  if (moduleCount.Probability <= 0.0f)
353  {
354  DebugConsole.AddWarning(
355  $"Potential error in outpost generation parameters \"{Identifier}\"."+
356  $" Probability of the module {moduleCount.Identifier} is 0 (the module should never spawn, so there's no reason to include it in the generation parameters.",
357  contentPackage: ContentPackage);
358  }
359  moduleCounts.Add(moduleCount);
360  }
361  moduleCount.Count = count;
362  if (probability.HasValue) { moduleCount.Probability = probability.Value; }
363  if (minDifficulty.HasValue) { moduleCount.MinDifficulty = minDifficulty.Value; }
364  if (maxDifficulty.HasValue) { moduleCount.MaxDifficulty = maxDifficulty.Value; }
365  }
366  }
367 
368  public void SetAllowedLocationTypes(IEnumerable<Identifier> allowedLocationTypes)
369  {
370  this.allowedLocationTypes.Clear();
371  foreach (Identifier locationType in allowedLocationTypes)
372  {
373  if (locationType == "any") { continue; }
374  this.allowedLocationTypes.Add(locationType);
375  }
376  }
377 
378  public IReadOnlyList<HumanPrefab> GetHumanPrefabs(IEnumerable<FactionPrefab> factions, Submarine sub, Rand.RandSync randSync)
379  {
380  if (!humanPrefabCollections.Any()) { return Array.Empty<HumanPrefab>(); }
381 
382  var collection = humanPrefabCollections.GetRandom(randSync);
383  return collection
384  .GetByFaction(factions)
385  .Where(humanPrefab => !humanPrefab.RequireSpawnPointTag || WayPoint.WayPointList.Any(wp => wp.Submarine == sub && humanPrefab.GetSpawnPointTags().Any(tag => wp.Tags.Contains(tag))))
386  .ToImmutableList();
387  }
388 
390  {
391  foreach (var collection in humanPrefabCollections)
392  {
393  foreach (var prefab in collection)
394  {
395  if (prefab != null && prefab.CampaignInteractionType == interactionType)
396  {
397  return true;
398  }
399  }
400  }
401  return false;
402  }
403 
404  public ImmutableHashSet<Identifier> GetStoreIdentifiers()
405  {
406  if (StoreIdentifiers == null)
407  {
408  var storeIdentifiers = new HashSet<Identifier>();
409  foreach (var collection in humanPrefabCollections)
410  {
411  foreach (var prefab in collection)
412  {
413  if (prefab?.CampaignInteractionType == CampaignMode.InteractionType.Store)
414  {
415  storeIdentifiers.Add(prefab.Identifier);
416  }
417  }
418  }
419  StoreIdentifiers = storeIdentifiers.ToImmutableHashSet();
420  }
421  return StoreIdentifiers;
422  }
423 
424  public override void Dispose() { }
425  }
426 }
Base class for content file types, which are loaded from filelist.xml via reflection....
Definition: ContentFile.cs:23
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
ContentPackage? ContentPackage
ContentPath? GetAttributeContentPath(string key)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
Dictionary< Identifier, SerializableProperty > SerializableProperties
OutpostGenerationParams(ContentXElement element, ContentFile file)
static readonly PrefabCollection< OutpostGenerationParams > OutpostParams
bool CanHaveCampaignInteraction(CampaignMode.InteractionType interactionType)
IReadOnlyList< ModuleCount > ModuleCounts
void SetAllowedLocationTypes(IEnumerable< Identifier > allowedLocationTypes)
IReadOnlyList< HumanPrefab > GetHumanPrefabs(IEnumerable< FactionPrefab > factions, Submarine sub, Rand.RandSync randSync)
ImmutableHashSet< Identifier > GetStoreIdentifiers()
void SetModuleCount(Identifier moduleFlag, int count, float? probability=null, float? minDifficulty=null, float? maxDifficulty=null)
int GetModuleCount(Identifier moduleFlag)
Dictionary< Identifier, SerializableProperty > SerializableProperties
IEnumerable< Identifier > AllowedLocationTypes
Identifiers of the location types this outpost can appear in. If empty, can appear in all types of lo...
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)