Server LuaCsForBarotrauma
MissionPrefab.cs
2 using System;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.Linq;
6 using System.Reflection;
7 using System.Xml.Linq;
8 
9 namespace Barotrauma
10 {
12  {
14 
19  public static readonly Dictionary<Identifier, Type> CoOpMissionClasses = new Dictionary<Identifier, Type>()
20  {
21  { "Salvage".ToIdentifier(), typeof(SalvageMission) },
22  { "Monster".ToIdentifier(), typeof(MonsterMission) },
23  { "Cargo".ToIdentifier(), typeof(CargoMission) },
24  { "Beacon".ToIdentifier(), typeof(BeaconMission) },
25  { "Nest".ToIdentifier(), typeof(NestMission) },
26  { "Mineral".ToIdentifier(), typeof(MineralMission) },
27  { "AbandonedOutpost".ToIdentifier(), typeof(AbandonedOutpostMission) },
28  { "Escort".ToIdentifier(), typeof(EscortMission) },
29  { "Pirate".ToIdentifier(), typeof(PirateMission) },
30  { "GoTo".ToIdentifier(), typeof(GoToMission) },
31  { "ScanAlienRuins".ToIdentifier(), typeof(ScanMission) },
32  { "EliminateTargets".ToIdentifier(), typeof(EliminateTargetsMission) },
33  { "End".ToIdentifier(), typeof(EndMission) }
34  };
35 
40  public static readonly Dictionary<Identifier, Type> PvPMissionClasses = new Dictionary<Identifier, Type>()
41  {
42  { "Combat".ToIdentifier(), typeof(CombatMission) }
43  };
44 
45  public static readonly HashSet<Identifier> HiddenMissionTypes = new HashSet<Identifier>() { "GoTo".ToIdentifier(), "End".ToIdentifier() };
46 
47  public class ReputationReward
48  {
49  public readonly Identifier FactionIdentifier;
50  public readonly float Amount;
51  public readonly float AmountForOpposingFaction;
52 
53  public ReputationReward(XElement element)
54  {
55  FactionIdentifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
56  Amount = element.GetAttributeFloat(nameof(Amount), 0.0f);
57  AmountForOpposingFaction = element.GetAttributeFloat(nameof(AmountForOpposingFaction), 0.0f);
58  }
59  }
60 
61  private readonly ConstructorInfo constructor;
62 
63  public readonly Identifier Type;
64 
65  public readonly Type MissionClass;
66 
67  public readonly bool MultiplayerOnly, SingleplayerOnly;
68 
69  public readonly Identifier TextIdentifier;
70 
71  public readonly ImmutableHashSet<Identifier> Tags;
72 
73  public readonly LocalizedString Name;
74  public readonly LocalizedString Description;
77  public readonly LocalizedString SonarLabel;
79 
81 
82  public readonly ImmutableList<ReputationReward> ReputationRewards;
83 
84  public readonly List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)>
85  DataRewards = new List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)>();
86 
87  public readonly int Commonness;
91  public readonly int? Difficulty;
92  public const int MinDifficulty = 1, MaxDifficulty = 4;
96  public readonly int MinLevelDifficulty = 0;
100  public readonly int MaxLevelDifficulty = 100;
101 
102  public readonly int Reward;
103 
104  // The titles and bodies of the popup messages during the mission, shown when the state of the mission changes. The order matters.
105  public readonly ImmutableArray<LocalizedString> Headers;
106  public readonly ImmutableArray<LocalizedString> Messages;
107 
108  public readonly bool AllowRetry;
109 
110  public readonly bool ShowInMenus, ShowStartMessage;
111 
112  public readonly bool IsSideObjective;
113 
114  public readonly bool AllowOtherMissionsInLevel;
115 
116  public readonly bool RequireWreck, RequireRuin, RequireBeaconStation, RequireThalamusWreck;
117  public readonly bool SpawnBeaconStationInMiddle;
118 
119  public readonly bool AllowOutpostNPCs;
120 
122 
123  public readonly RespawnMode? ForceRespawnMode;
124 
129 
130  public readonly bool LoadSubmarines = true;
131 
135  public readonly bool BlockLocationTypeChanges;
136 
137  public readonly bool ShowProgressBar;
138  public readonly bool ShowProgressInNumbers;
139  public readonly int MaxProgressState;
141 
145  public readonly List<(Identifier from, Identifier to)> AllowedConnectionTypes;
146 
150  public readonly List<Identifier> AllowedLocationTypes = new List<Identifier>();
151 
156 
160  public readonly List<string> UnhideEntitySubCategories = new List<string>();
161 
162  public class TriggerEvent
163  {
164  [Serialize("", IsPropertySaveable.Yes)]
165  public Identifier EventIdentifier { get; private set; }
166 
167  [Serialize("", IsPropertySaveable.Yes)]
168  public Identifier EventTag { get; private set; }
169 
170  [Serialize(0, IsPropertySaveable.Yes)]
171  public int State { get; private set; }
172 
173  [Serialize(0.0f, IsPropertySaveable.Yes)]
174  public float Delay { get; private set; }
175 
176  [Serialize(false, IsPropertySaveable.Yes)]
177  public bool CampaignOnly { get; private set; }
178 
179  public TriggerEvent(XElement element)
180  {
182  }
183  }
184 
185  public readonly List<TriggerEvent> TriggerEvents = new List<TriggerEvent>();
186 
188 
190 
191  public MissionPrefab(ContentXElement element, MissionsFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
192  {
193  ConfigElement = element;
194 
195  TextIdentifier = element.GetAttributeIdentifier("textidentifier", Identifier);
196 
197  Tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToImmutableHashSet();
198 
199  Name = GetText(element.GetAttributeString("name", ""), "MissionName");
200  Description = GetText(element.GetAttributeString("description", ""), "MissionDescription");
201 
202  LocalizedString GetText(string textTag, string textTagPrefix)
203  {
204  if (string.IsNullOrEmpty(textTag))
205  {
206  return TextManager.Get($"{textTagPrefix}.{TextIdentifier}");
207  }
208  else
209  {
210  return
211  //prefer finding a text based on the specific text tag defined in the mission config
212  TextManager.Get(textTag)
213  //2nd option: the "default" format (MissionName.SomeMission)
214  .Fallback(TextManager.Get($"{textTagPrefix}.{TextIdentifier}"))
215  //last option: use the text in the xml as-is with no localization
216  .Fallback(textTag);
217  }
218  }
219 
220  Reward = element.GetAttributeInt("reward", 1);
221  AllowRetry = element.GetAttributeBool("allowretry", false);
222  ShowInMenus = element.GetAttributeBool("showinmenus", true);
223  ShowStartMessage = element.GetAttributeBool("showstartmessage", true);
224  IsSideObjective = element.GetAttributeBool("sideobjective", false);
225 
226  RequireWreck = element.GetAttributeBool(nameof(RequireWreck), false);
227  RequireThalamusWreck = element.GetAttributeBool(nameof(RequireThalamusWreck), false);
228  RequireRuin = element.GetAttributeBool(nameof(RequireRuin), false);
229  RequireBeaconStation = element.GetAttributeBool(nameof(RequireBeaconStation), false);
231  if (RequireThalamusWreck) { RequireWreck = true; }
232 
233  LoadSubmarines = element.GetAttributeBool(nameof(LoadSubmarines), true);
234 
237  Commonness = element.GetAttributeInt("commonness", 1);
238  AllowOtherMissionsInLevel = element.GetAttributeBool("allowothermissionsinlevel", true);
239  if (element.GetAttribute("difficulty") != null)
240  {
241  int difficulty = element.GetAttributeInt("difficulty", MinDifficulty);
242  Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
243  }
246  MinLevelDifficulty = Math.Clamp(MinLevelDifficulty, 0, Math.Min(MaxLevelDifficulty, 100));
247  MaxLevelDifficulty = Math.Clamp(MaxLevelDifficulty, Math.Max(MinLevelDifficulty, 0), 100);
248 
249  AllowOutpostNPCs = element.GetAttributeBool(nameof(AllowOutpostNPCs), true);
252 
253  if (element.GetAttribute(nameof(ForceRespawnMode)) != null)
254  {
255  ForceRespawnMode = element.GetAttributeEnum(nameof(ForceRespawnMode), RespawnMode.MidRound);
256  }
257 
258  ShowProgressBar = element.GetAttributeBool(nameof(ShowProgressBar), false);
260  MaxProgressState = element.GetAttributeInt(nameof(MaxProgressState), 1);
261  string progressBarLabel = element.GetAttributeString(nameof(ProgressBarLabel), "");
262  ProgressBarLabel = TextManager.Get(progressBarLabel).Fallback(progressBarLabel);
263 
264  string successMessageTag = element.GetAttributeString("successmessage", "");
265  SuccessMessage = TextManager.Get($"MissionSuccess.{TextIdentifier}");
266  if (!string.IsNullOrEmpty(successMessageTag))
267  {
269  .Fallback(TextManager.Get(successMessageTag))
270  .Fallback(successMessageTag);
271  }
272  SuccessMessage = SuccessMessage.Fallback(TextManager.Get("missioncompleted"));
273 
274  string failureMessageTag = element.GetAttributeString("failuremessage", "");
275  FailureMessage = TextManager.Get($"MissionFailure.{TextIdentifier}");
276  if (!string.IsNullOrEmpty(failureMessageTag))
277  {
279  .Fallback(TextManager.Get(failureMessageTag))
280  .Fallback(failureMessageTag);
281  }
282  FailureMessage = FailureMessage.Fallback(TextManager.Get("missionfailed"));
283 
284  string sonarLabelTag = element.GetAttributeString("sonarlabel", "");
285  SonarLabel =
286  TextManager.Get($"MissionSonarLabel.{sonarLabelTag}")
287  .Fallback(TextManager.Get(sonarLabelTag))
288  .Fallback(TextManager.Get($"MissionSonarLabel.{TextIdentifier}"));
289  if (!string.IsNullOrEmpty(sonarLabelTag))
290  {
291  SonarLabel = SonarLabel.Fallback(sonarLabelTag);
292  }
293 
294  SonarIconIdentifier = element.GetAttributeIdentifier("sonaricon", "");
295 
296  MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false);
297  SingleplayerOnly = element.GetAttributeBool("singleplayeronly", false);
298 
299  AchievementIdentifier = element.GetAttributeIdentifier("achievementidentifier", "");
300 
301  UnhideEntitySubCategories = element.GetAttributeStringArray("unhideentitysubcategories", Array.Empty<string>()).ToList();
302 
303  var headers = new List<LocalizedString>();
304  var messages = new List<LocalizedString>();
305  AllowedConnectionTypes = new List<(Identifier from, Identifier to)>();
306 
307  for (int i = 0; i < 100; i++)
308  {
309  LocalizedString header = TextManager.Get($"MissionHeader{i}.{TextIdentifier}");
310  LocalizedString message = TextManager.Get($"MissionMessage{i}.{TextIdentifier}");
311  if (!message.IsNullOrEmpty())
312  {
313  headers.Add(header);
314  messages.Add(message);
315  }
316  }
317 
318  List<ReputationReward> reputationRewards = new List<ReputationReward>();
319  int messageIndex = 0;
320  foreach (var subElement in element.Elements())
321  {
322  switch (subElement.Name.ToString().ToLowerInvariant())
323  {
324  case "message":
325  if (messageIndex > headers.Count - 1)
326  {
327  headers.Add(string.Empty);
328  messages.Add(string.Empty);
329  }
330  headers[messageIndex] =
331  TextManager.Get($"MissionHeader{messageIndex}.{TextIdentifier}")
332  .Fallback(TextManager.Get(subElement.GetAttributeString("header", "")))
333  .Fallback(subElement.GetAttributeString("header", ""));
334  messages[messageIndex] =
335  TextManager.Get($"MissionMessage{messageIndex}.{TextIdentifier}")
336  .Fallback(TextManager.Get(subElement.GetAttributeString("text", "")))
337  .Fallback(subElement.GetAttributeString("text", ""));
338  messageIndex++;
339  break;
340  case "locationtype":
341  case "connectiontype":
342  if (subElement.GetAttribute("identifier") != null)
343  {
344  AllowedLocationTypes.Add(subElement.GetAttributeIdentifier("identifier", ""));
345  }
346  else
347  {
349  subElement.GetAttributeIdentifier("from", ""),
350  subElement.GetAttributeIdentifier("to", "")));
351  }
352  break;
353  case "locationtypechange":
354  LocationTypeChangeOnCompleted = new LocationTypeChange(subElement.GetAttributeIdentifier("from", ""), subElement, requireChangeMessages: false, defaultProbability: 1.0f);
355  break;
356  case "reputation":
357  case "reputationreward":
358  reputationRewards.Add(new ReputationReward(subElement));
359  break;
360  case "metadata":
361  Identifier identifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
362  string stringValue = subElement.GetAttributeString("value", string.Empty);
363  if (!string.IsNullOrWhiteSpace(stringValue) && !identifier.IsEmpty)
364  {
365  object value = SetDataAction.ConvertXMLValue(stringValue);
367 
368  string operatingString = subElement.GetAttributeString("operation", string.Empty);
369  if (!string.IsNullOrWhiteSpace(operatingString))
370  {
371  operation = (SetDataAction.OperationType) Enum.Parse(typeof(SetDataAction.OperationType), operatingString);
372  }
373 
374  DataRewards.Add((identifier, value, operation));
375  }
376  break;
377  case "triggerevent":
378  TriggerEvents.Add(new TriggerEvent(subElement));
379  break;
380  }
381  }
382  Headers = headers.ToImmutableArray();
383  Messages = messages.ToImmutableArray();
384  ReputationRewards = reputationRewards.ToImmutableList();
385 
386  MissionClass = FindMissionClass(element);
387  Type = element.GetAttributeIdentifier(nameof(Type), Identifier.Empty);
388 
389 #if DEBUG
390  if (MissionClass == typeof(MonsterMission) && SonarLabel.IsNullOrEmpty())
391  {
392  DebugConsole.AddWarning($"Potential error in mission prefab \"{Identifier}\" - sonar label not set.");
393  }
394 #endif
395 
396  if (!LoadSubmarines && MissionClass != typeof(CombatMission))
397  {
398  DebugConsole.AddWarning($"Potential error in mission {Identifier}: Disabling submarines is only intended for combat missions taking place in an outpost, and may lead to issues in other types of missions.",
399  contentPackage: element.ContentPackage);
400  }
401 
402  constructor = FindMissionConstructor(element, MissionClass);
403  if (constructor == null)
404  {
405  DebugConsole.ThrowError($"Failed to find a constructor for the mission type \"{Type}\"!",
406  contentPackage: element.ContentPackage);
407  }
408 
409  InitProjSpecific(element);
410  }
411 
412  private Type FindMissionClass(ContentXElement element)
413  {
414  Type type;
415  Identifier typeName = element.NameAsIdentifier();
416  type = TryGetClass(typeName.RemoveFromEnd("Mission"));
417 
418  if (type == null)
419  {
420  //backwards compatibility: the actual mission class used to be defined by the "type" attribute,
421  //Now the mission class is defined by the name of the mission element, and the type can be any arbitrary string,
422  //but if we failed to find the class based on the name, let's try the type attribute.
423  Identifier typeNameLegacy = (element.GetAttributeIdentifier("type", Identifier.Empty)).ToIdentifier();
424  if (typeNameLegacy == "OutpostDestroy" || typeNameLegacy == "OutpostRescue")
425  {
426  typeNameLegacy = "AbandonedOutpost".ToIdentifier();
427  }
428  else if (typeNameLegacy == "clearalienruins")
429  {
430  typeNameLegacy = "EliminateTargets".ToIdentifier();
431  }
432  type = TryGetClass(typeNameLegacy) ?? TryGetClass(typeNameLegacy.AppendIfMissing("Mission"));
433  if (type == null)
434  {
435  DebugConsole.ThrowError($"Failed to find the mission type \"{typeNameLegacy}\" for the mission {Identifier}.",
436  contentPackage: element.ContentPackage);
437  return null;
438  }
439  }
440 
441  static Type TryGetClass(Identifier typeName)
442  {
443  if (CoOpMissionClasses.TryGetValue(typeName, out Type coOpMissionClass))
444  {
445  return coOpMissionClass;
446  }
447  else if (PvPMissionClasses.TryGetValue(typeName, out Type pvpMissionClass))
448  {
449  return pvpMissionClass;
450  }
451  return null;
452  }
453 
454  return type;
455  }
456 
457  private ConstructorInfo FindMissionConstructor(ContentXElement element, Type missionClass)
458  {
459  ConstructorInfo constructor;
460  if (missionClass == null) { return null; }
461  if (missionClass != typeof(Mission) && !missionClass.IsSubclassOf(typeof(Mission))) { return null; }
462  constructor = missionClass.GetConstructor(new Type[] { typeof(MissionPrefab), typeof(Location[]), typeof(Submarine) });
463  if (constructor == null)
464  {
465  DebugConsole.ThrowError(
466  $"Could not find the constructor of the mission type \"{missionClass}\" for the mission {Identifier}",
467  contentPackage: element.ContentPackage);
468  return null;
469  }
470  return constructor;
471  }
472 
473  partial void InitProjSpecific(ContentXElement element);
474 
475  public bool IsAllowed(Location from, Location to)
476  {
477  if (from == to)
478  {
479  if (!RequiredLocationFaction.IsEmpty && from.Faction?.Prefab.Identifier != RequiredLocationFaction)
480  {
481  return false;
482  }
483  return
484  AllowedLocationTypes.Any(lt => lt == "any") ||
485  AllowedLocationTypes.Any(lt => lt == Barotrauma.Tags.AnyOutpost && from.HasOutpost() && from.Type.IsAnyOutpost) ||
486  AllowedLocationTypes.Any(lt => lt == from.Type.Identifier);
487  }
488 
489  foreach (var (fromType, toType) in AllowedConnectionTypes)
490  {
491  if (fromType == "any" ||
492  fromType == from.Type.Identifier ||
493  (fromType == Barotrauma.Tags.AnyOutpost && from.HasOutpost() && from.Type.IsAnyOutpost && from.Type.Identifier != "abandoned"))
494  {
495  if (toType == "any" ||
496  toType == to.Type.Identifier ||
497  (toType == Barotrauma.Tags.AnyOutpost && to.HasOutpost() && to.Type.IsAnyOutpost && to.Type.Identifier != "abandoned"))
498  {
499  return true;
500  }
501  }
502  }
503 
504  return false;
505  }
506 
510  public bool IsAllowedDifficulty(float difficulty) => difficulty >= MinLevelDifficulty && difficulty <= MaxLevelDifficulty;
511 
512  public Mission Instantiate(Location[] locations, Submarine sub)
513  {
514  return constructor?.Invoke(new object[] { this, locations, sub }) as Mission;
515  }
516 
517  partial void DisposeProjectSpecific();
518  public override void Dispose()
519  {
520  DisposeProjectSpecific();
521  }
522 
527  public static IEnumerable<Identifier> GetAllMultiplayerSelectableMissionTypes()
528  {
529  List<Identifier> missionTypes = new List<Identifier>();
530  foreach (var missionPrefab in Prefabs)
531  {
532  if (missionPrefab.Commonness <= 0.0f) { continue; }
533  if (missionPrefab.SingleplayerOnly) { continue; }
534  if (HiddenMissionTypes.Contains(missionPrefab.Type))
535  {
536  continue;
537  }
538  if (!missionTypes.Contains(missionPrefab.Type))
539  {
540  missionTypes.Add(missionPrefab.Type);
541  }
542  }
543  return missionTypes.OrderBy(t => t.Value);
544  }
545  }
546 }
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
ContentPackage? ContentPackage
Identifier NameAsIdentifier()
bool GetAttributeBool(string key, bool def)
int GetAttributeInt(string key, int def)
string?[] GetAttributeStringArray(string key, string[]? def, bool convertToLowerInvariant=false)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
FactionPrefab Prefab
Definition: Factions.cs:18
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
LocationType Type
Definition: Location.cs:94
bool IsAnyOutpost
Is this location type considered valid for e.g. events and missions that are should be available in "...
Definition: LocationType.cs:39
readonly Identifier AchievementIdentifier
readonly LocalizedString SonarLabel
readonly ImmutableList< ReputationReward > ReputationRewards
bool IsAllowed(Location from, Location to)
override void Dispose()
readonly? RespawnMode ForceRespawnMode
readonly? int Difficulty
Displayed difficulty (indicator)
readonly int MaxLevelDifficulty
The actual maximum difficulty of the level allowed for this mission to trigger.
static readonly Dictionary< Identifier, Type > CoOpMissionClasses
The keys here are for backwards compatibility, tying the old mission types to the appropriate class....
static readonly PrefabCollection< MissionPrefab > Prefabs
static readonly Dictionary< Identifier, Type > PvPMissionClasses
The keys here are for backwards compatibility, tying the old mission types to the appropriate class....
readonly Identifier SonarIconIdentifier
readonly bool AllowOtherMissionsInLevel
static readonly HashSet< Identifier > HiddenMissionTypes
readonly List< TriggerEvent > TriggerEvents
readonly int MaxProgressState
readonly List<(Identifier from, Identifier to)> AllowedConnectionTypes
The mission can only be received when travelling from a location of the first type to a location of t...
readonly bool BlockLocationTypeChanges
If enabled, locations this mission takes place in cannot change their type
readonly int MinLevelDifficulty
The actual minimum difficulty of the level allowed for this mission to trigger.
readonly LocalizedString ProgressBarLabel
readonly ImmutableArray< LocalizedString > Headers
LocationTypeChange LocationTypeChangeOnCompleted
readonly List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)> DataRewards
readonly ImmutableHashSet< Identifier > Tags
readonly Identifier RequiredLocationFaction
The mission can only happen in locations owned by this faction. In the mission mode,...
readonly bool RequireWreck
readonly LocalizedString Name
readonly Type MissionClass
readonly LocalizedString Description
readonly ImmutableArray< LocalizedString > Messages
readonly bool MultiplayerOnly
readonly bool IsSideObjective
readonly List< Identifier > AllowedLocationTypes
The mission can only be received in these location types
static IEnumerable< Identifier > GetAllMultiplayerSelectableMissionTypes()
Returns all mission types that can be selected e.g. in the server lobby, excluding any special,...
readonly LocalizedString FailureMessage
readonly bool LoadSubmarines
MissionPrefab(ContentXElement element, MissionsFile file)
readonly LocalizedString SuccessMessage
bool IsAllowedDifficulty(float difficulty)
Inclusive (matching the min an max values is accepted).
readonly List< string > UnhideEntitySubCategories
Show entities belonging to these sub categories when the mission starts
readonly Identifier AllowOutpostSelectionFromTag
If set, the players can choose which outpost is used for the mission (selected from the outposts that...
readonly bool AllowOutpostNPCs
readonly bool ShowProgressBar
readonly Identifier TextIdentifier
readonly Identifier Type
readonly bool ShowProgressInNumbers
readonly ContentXElement ConfigElement
readonly bool SpawnBeaconStationInMiddle
readonly Identifier ForceOutpostGenerationParameters
Mission Instantiate(Location[] locations, Submarine sub)
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)
Sets a campaign metadata value. The metadata can be any arbitrary data you want to save: for example,...
Definition: SetDataAction.cs:9
static object ConvertXMLValue(string value)