Client LuaCsForBarotrauma
LocationType.cs
2 using Barotrauma.IO;
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.Linq;
10 
11 namespace Barotrauma
12 {
14  {
16 
17  private readonly ImmutableArray<string> rawNames;
18  private readonly ImmutableArray<Sprite> portraits;
19 
20  //<name, commonness>
21  private readonly ImmutableArray<(Identifier Identifier, float Commonness, bool AlwaysAvailableIfMissingFromCrew)> hireableJobs;
22  private readonly float totalHireableWeight;
23 
24  public readonly Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
25  public readonly Dictionary<int, int> MinCountPerZone = new Dictionary<int, int>();
26 
27  public readonly LocalizedString Name;
28  public readonly LocalizedString Description;
29 
30  public readonly Identifier ForceLocationName;
31 
32  public readonly float BeaconStationChance;
33 
34  public readonly CharacterTeamType OutpostTeam;
35 
36  public readonly List<LocationTypeChange> CanChangeTo = new List<LocationTypeChange>();
37 
38  public readonly ImmutableArray<Identifier> MissionIdentifiers;
39  public readonly ImmutableArray<Identifier> MissionTags;
40 
41  public readonly List<string> HideEntitySubcategories = new List<string>();
42 
43  public bool IsEnterable { get; private set; }
44 
45  public bool AllowAsBiomeGate { get; private set; }
46 
50  public bool AllowInRandomLevels { get; private set; }
51 
53  {
54  get;
55  private set;
56  }
57 
58  private readonly ImmutableArray<Identifier>? nameIdentifiers = null;
59 
60  private LanguageIdentifier nameFormatLanguage;
61 
62  private ImmutableArray<string>? nameFormats = null;
63  public IReadOnlyList<string> NameFormats
64  {
65  get
66  {
67  if (nameFormats == null || GameSettings.CurrentConfig.Language != nameFormatLanguage)
68  {
69  nameFormats = TextManager.GetAll($"LocationNameFormat.{Identifier}").ToImmutableArray();
70  nameFormatLanguage = GameSettings.CurrentConfig.Language;
71  }
72  return nameFormats;
73  }
74  }
75 
77  {
78  get { return hireableJobs.Any(); }
79  }
80 
81  public bool HasOutpost
82  {
83  get;
84  private set;
85  }
86 
88 
92  public Identifier Faction { get; }
93 
97  public Identifier SecondaryFaction { get; }
98 
99  public Sprite Sprite { get; private set; }
100  public Sprite RadiationSprite { get; }
101 
102  private readonly Identifier forceOutpostGenerationParamsIdentifier;
103 
107  public bool IgnoreGenericEvents { get; }
108 
109  public Color SpriteColor
110  {
111  get;
112  private set;
113  }
114 
115  public float StoreMaxReputationModifier { get; } = 0.1f;
116  public float StoreSellPriceModifier { get; } = 0.3f;
117  public float DailySpecialPriceModifier { get; } = 0.5f;
118  public float RequestGoodPriceModifier { get; } = 2f;
119  public int StoreInitialBalance { get; } = 5000;
123  public int StorePriceModifierRange { get; } = 5;
124  public int DailySpecialsCount { get; } = 1;
125  public int RequestedGoodsCount { get; } = 1;
126 
127  public readonly bool ShowSonarMarker = true;
128 
129  public override string ToString()
130  {
131  return $"LocationType (" + Identifier + ")";
132  }
133 
134  public LocationType(ContentXElement element, LocationTypesFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName))
135  {
136  Name = TextManager.Get("LocationName." + Identifier, "unknown");
137  Description = TextManager.Get("LocationDescription." + Identifier, "");
138 
139  BeaconStationChance = element.GetAttributeFloat("beaconstationchance", 0.0f);
140 
142  HasOutpost = element.GetAttributeBool("hasoutpost", true);
143  IsEnterable = element.GetAttributeBool("isenterable", HasOutpost);
144  AllowAsBiomeGate = element.GetAttributeBool(nameof(AllowAsBiomeGate), true);
146 
147  Faction = element.GetAttributeIdentifier(nameof(Faction), Identifier.Empty);
149 
150  ShowSonarMarker = element.GetAttributeBool("showsonarmarker", true);
151 
152  MissionIdentifiers = element.GetAttributeIdentifierArray("missionidentifiers", Array.Empty<Identifier>()).ToImmutableArray();
153  MissionTags = element.GetAttributeIdentifierArray("missiontags", Array.Empty<Identifier>()).ToImmutableArray();
154 
155  HideEntitySubcategories = element.GetAttributeStringArray("hideentitysubcategories", Array.Empty<string>()).ToList();
156 
158 
159  forceOutpostGenerationParamsIdentifier = element.GetAttributeIdentifier("forceoutpostgenerationparams", Identifier.Empty);
160 
161  IgnoreGenericEvents = element.GetAttributeBool(nameof(IgnoreGenericEvents), false);
162 
163  string teamStr = element.GetAttributeString("outpostteam", "FriendlyNPC");
164  Enum.TryParse(teamStr, out OutpostTeam);
165 
166  if (element.GetAttribute("name") != null)
167  {
168  ForceLocationName = element.GetAttributeIdentifier("name", string.Empty);
169  }
170  else
171  {
172  var names = new List<string>();
173  //backwards compatibility for location names defined in a text file
174  string[] rawNamePaths = element.GetAttributeStringArray("namefile", Array.Empty<string>());
175  if (rawNamePaths.Any())
176  {
177  foreach (string rawPath in rawNamePaths)
178  {
179  try
180  {
181  var path = ContentPath.FromRaw(element.ContentPackage, rawPath.Trim());
182  names.AddRange(File.ReadAllLines(path.Value).ToList());
183  }
184  catch (Exception e)
185  {
186  DebugConsole.ThrowError($"Failed to read name file \"rawPath\" for location type \"{Identifier}\"!", e);
187  }
188  }
189  if (!names.Any())
190  {
191  names.Add("ERROR: No names found");
192  }
193  this.rawNames = names.ToImmutableArray();
194  }
195  else
196  {
197  nameIdentifiers = element.GetAttributeIdentifierArray("nameidentifiers", new Identifier[] { Identifier }).ToImmutableArray();
198  }
199  }
200 
201  string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", Array.Empty<string>());
202  foreach (string commonnessPerZoneStr in commonnessPerZoneStrs)
203  {
204  string[] splitCommonnessPerZone = commonnessPerZoneStr.Split(':');
205  if (splitCommonnessPerZone.Length != 2 ||
206  !int.TryParse(splitCommonnessPerZone[0].Trim(), out int zoneIndex) ||
207  !float.TryParse(splitCommonnessPerZone[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float zoneCommonness))
208  {
209  DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Identifier + "\" - commonness should be given in the format \"zone1index: zone1commonness, zone2index: zone2commonness\"");
210  break;
211  }
212  CommonnessPerZone[zoneIndex] = zoneCommonness;
213  }
214 
215  string[] minCountPerZoneStrs = element.GetAttributeStringArray("mincountperzone", Array.Empty<string>());
216  foreach (string minCountPerZoneStr in minCountPerZoneStrs)
217  {
218  string[] splitMinCountPerZone = minCountPerZoneStr.Split(':');
219  if (splitMinCountPerZone.Length != 2 ||
220  !int.TryParse(splitMinCountPerZone[0].Trim(), out int zoneIndex) ||
221  !int.TryParse(splitMinCountPerZone[1].Trim(), out int minCount))
222  {
223  DebugConsole.ThrowError("Failed to read minimum count values for location type \"" + Identifier + "\" - minimum counts should be given in the format \"zone1index: zone1mincount, zone2index: zone2mincount\"");
224  break;
225  }
226  MinCountPerZone[zoneIndex] = minCount;
227  }
228  var portraits = new List<Sprite>();
229  var hireableJobs = new List<(Identifier, float, bool)>();
230  foreach (var subElement in element.Elements())
231  {
232  switch (subElement.Name.ToString().ToLowerInvariant())
233  {
234  case "hireable":
235  Identifier jobIdentifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
236  float jobCommonness = subElement.GetAttributeFloat("commonness", 1.0f);
237  bool availableIfMissing = subElement.GetAttributeBool("AlwaysAvailableIfMissingFromCrew", false);
238  totalHireableWeight += jobCommonness;
239  hireableJobs.Add((jobIdentifier, jobCommonness, availableIfMissing));
240  break;
241  case "symbol":
242  Sprite = new Sprite(subElement, lazyLoad: true);
243  SpriteColor = subElement.GetAttributeColor("color", Color.White);
244  break;
245  case "radiationsymbol":
246  RadiationSprite = new Sprite(subElement, lazyLoad: true);
247  break;
248  case "changeto":
249  CanChangeTo.Add(new LocationTypeChange(Identifier, subElement, requireChangeMessages: true));
250  break;
251  case "portrait":
252  var portrait = new Sprite(subElement, lazyLoad: true);
253  if (portrait != null)
254  {
255  portraits.Add(portrait);
256  }
257  break;
258  case "store":
259  StoreMaxReputationModifier = subElement.GetAttributeFloat("maxreputationmodifier", StoreMaxReputationModifier);
260  StoreSellPriceModifier = subElement.GetAttributeFloat("sellpricemodifier", StoreSellPriceModifier);
261  DailySpecialPriceModifier = subElement.GetAttributeFloat("dailyspecialpricemodifier", DailySpecialPriceModifier);
262  RequestGoodPriceModifier = subElement.GetAttributeFloat("requestgoodpricemodifier", RequestGoodPriceModifier);
263  StoreInitialBalance = subElement.GetAttributeInt("initialbalance", StoreInitialBalance);
264  StorePriceModifierRange = subElement.GetAttributeInt("pricemodifierrange", StorePriceModifierRange);
265  DailySpecialsCount = subElement.GetAttributeInt("dailyspecialscount", DailySpecialsCount);
266  RequestedGoodsCount = subElement.GetAttributeInt("requestedgoodscount", RequestedGoodsCount);
267  break;
268  }
269  }
270  this.portraits = portraits.ToImmutableArray();
271  this.hireableJobs = hireableJobs.ToImmutableArray();
272  }
273 
274  public IEnumerable<JobPrefab> GetHireablesMissingFromCrew()
275  {
276  if (GameMain.GameSession?.CrewManager != null)
277  {
278  var missingJobs = hireableJobs
279  .Where(j => j.AlwaysAvailableIfMissingFromCrew)
280  .Where(j => GameMain.GameSession.CrewManager.GetCharacterInfos().None(c => c.Job?.Prefab.Identifier == j.Identifier));
281  if (missingJobs.Any())
282  {
283  foreach (var missingJob in missingJobs)
284  {
285  if (JobPrefab.Prefabs.TryGet(missingJob.Identifier, out JobPrefab job))
286  {
287  yield return job;
288  }
289  }
290  }
291  }
292  }
293 
295  {
296  Identifier selectedJobId = hireableJobs.GetRandomByWeight(j => j.Commonness, Rand.RandSync.ServerAndClient).Identifier;
297  if (JobPrefab.Prefabs.TryGet(selectedJobId, out JobPrefab job))
298  {
299  return job;
300  }
301  return null;
302  }
303 
304  public Sprite GetPortrait(int randomSeed)
305  {
306  if (portraits.Length == 0) { return null; }
307  return portraits[Math.Abs(randomSeed) % portraits.Length];
308  }
309 
310  public Identifier GetRandomNameId(Random rand, IEnumerable<Location> existingLocations)
311  {
312  if (nameIdentifiers == null)
313  {
314  return Identifier.Empty;
315  }
316  List<Identifier> nameIds = new List<Identifier>();
317  foreach (var nameId in nameIdentifiers)
318  {
319  int index = 0;
320  while (true)
321  {
322  Identifier tag = $"LocationName.{nameId}.{index}".ToIdentifier();
323  if (TextManager.ContainsTag(tag, TextManager.DefaultLanguage))
324  {
325  nameIds.Add(tag);
326  index++;
327  }
328  else
329  {
330  if (index == 0)
331  {
332  DebugConsole.ThrowError($"Could not find any location names for the location type {Identifier}. Name identifier: {nameId}");
333  }
334  break;
335  }
336  }
337  }
338  if (nameIds.None())
339  {
340  return Identifier.Empty;
341  }
342  if (existingLocations != null)
343  {
344  var unusedNameIds = nameIds.FindAll(nameId => existingLocations.None(l => l.NameIdentifier == nameId));
345  if (unusedNameIds.Count > 0)
346  {
347  return unusedNameIds[rand.Next() % unusedNameIds.Count];
348  }
349  }
350  return nameIds[rand.Next() % nameIds.Count];
351  }
352 
356  public string GetRandomRawName(Random rand, IEnumerable<Location> existingLocations)
357  {
358  if (rawNames == null || rawNames.None()) { return string.Empty; }
359  if (existingLocations != null)
360  {
361  var unusedNames = rawNames.Where(name => !existingLocations.Any(l => l.DisplayName.Value == name)).ToList();
362  if (unusedNames.Count > 0)
363  {
364  return unusedNames[rand.Next() % unusedNames.Count];
365  }
366  }
367  return rawNames[rand.Next() % rawNames.Length];
368  }
369 
370  public static LocationType Random(Random rand, int? zone = null, bool requireOutpost = false, Func<LocationType, bool> predicate = null)
371  {
372  Debug.Assert(Prefabs.Any(), "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
373 
374  LocationType[] allowedLocationTypes =
375  Prefabs.Where(lt =>
376  (predicate == null || predicate(lt)) && IsValid(lt))
377  .OrderBy(p => p.UintIdentifier).ToArray();
378 
379  bool IsValid(LocationType lt)
380  {
381  if (requireOutpost && !lt.HasOutpost) { return false; }
382  if (zone.HasValue)
383  {
384  if (!lt.CommonnessPerZone.ContainsKey(zone.Value)) { return false; }
385  }
386  //if zone is not defined, this is a "random" (non-campaign) level
387  //-> don't choose location types that aren't allowed in those
388  else if (!lt.AllowInRandomLevels)
389  {
390  return false;
391  }
392  return true;
393  }
394 
395  if (allowedLocationTypes.Length == 0)
396  {
397  DebugConsole.ThrowError("Could not generate a random location type - no location types for the zone " + zone + " found!");
398  }
399 
400  if (zone.HasValue)
401  {
402  return ToolBox.SelectWeightedRandom(
403  allowedLocationTypes,
404  allowedLocationTypes.Select(a => a.CommonnessPerZone[zone.Value]).ToArray(),
405  rand);
406  }
407  else
408  {
409  return allowedLocationTypes[rand.Next() % allowedLocationTypes.Length];
410  }
411  }
412 
414  {
415  if (OutpostGenerationParams.OutpostParams.TryGet(forceOutpostGenerationParamsIdentifier, out var parameters))
416  {
417  return parameters;
418  }
419  return null;
420  }
421 
422  public override void Dispose() { }
423  }
424 }
static ContentPath FromRaw(string? rawValue)
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
float GetAttributeFloat(string key, float def)
ContentPackage? ContentPackage
bool GetAttributeBool(string key, bool def)
string?[] GetAttributeStringArray(string key, string[]? def, bool convertToLowerInvariant=false)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
IEnumerable< CharacterInfo > GetCharacterInfos()
Note: this only returns AI characters' infos in multiplayer. The infos are used to manage hiring/firi...
static GameSession?? GameSession
Definition: GameMain.cs:88
static readonly PrefabCollection< JobPrefab > Prefabs
readonly LocalizedString Description
Definition: LocationType.cs:28
readonly CharacterTeamType OutpostTeam
Definition: LocationType.cs:34
readonly ImmutableArray< Identifier > MissionIdentifiers
Definition: LocationType.cs:38
JobPrefab GetRandomHireable()
readonly Identifier ForceLocationName
Definition: LocationType.cs:30
IReadOnlyList< string > NameFormats
Definition: LocationType.cs:64
Identifier SecondaryFaction
If set, forces the location to be assigned to this secondary faction. Set to "None" if you don't want...
Definition: LocationType.cs:97
Sprite GetPortrait(int randomSeed)
readonly List< string > HideEntitySubcategories
Definition: LocationType.cs:41
readonly ImmutableArray< Identifier > MissionTags
Definition: LocationType.cs:39
static LocationType Random(Random rand, int? zone=null, bool requireOutpost=false, Func< LocationType, bool > predicate=null)
readonly Dictionary< int, int > MinCountPerZone
Definition: LocationType.cs:25
readonly float BeaconStationChance
Definition: LocationType.cs:32
readonly LocalizedString Name
Definition: LocationType.cs:27
LocationType(ContentXElement element, LocationTypesFile file)
readonly List< LocationTypeChange > CanChangeTo
Definition: LocationType.cs:36
readonly Dictionary< int, float > CommonnessPerZone
Definition: LocationType.cs:24
OutpostGenerationParams GetForcedOutpostGenerationParams()
bool IgnoreGenericEvents
If set to true, only event sets that explicitly define this location type in EventSet....
override string ToString()
static readonly PrefabCollection< LocationType > Prefabs
Definition: LocationType.cs:15
readonly bool ShowSonarMarker
int StorePriceModifierRange
In percentages
bool AllowInRandomLevels
Can this location type be used in the random, non-campaign levels that don't take place in any specif...
Definition: LocationType.cs:50
IEnumerable< JobPrefab > GetHireablesMissingFromCrew()
bool UsePortraitInRandomLoadingScreens
Definition: LocationType.cs:53
Identifier GetRandomNameId(Random rand, IEnumerable< Location > existingLocations)
override void Dispose()
string GetRandomRawName(Random rand, IEnumerable< Location > existingLocations)
For backwards compatibility. Chooses a random name from the names defined in the ....
Identifier ReplaceInRadiation
Definition: LocationType.cs:87
static readonly PrefabCollection< OutpostGenerationParams > OutpostParams
readonly Identifier Identifier
Definition: Prefab.cs:34
Prefab that has a property serves as a deterministic hash of a prefab's identifier....