Client LuaCsForBarotrauma
TraitorEventPrefab.cs
1 #nullable enable
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Globalization;
8 using System.Linq;
9 using System.Xml.Linq;
10 
11 namespace Barotrauma
12 {
14  {
15  class MissionRequirement
16  {
17  public Identifier MissionIdentifier;
18  public Identifier MissionTag;
19  public MissionType MissionType;
20 
21  public MissionRequirement(XElement element, TraitorEventPrefab prefab)
22  {
23  MissionIdentifier = element.GetAttributeIdentifier(nameof(MissionIdentifier), Identifier.Empty);
24  MissionTag = element.GetAttributeIdentifier(nameof(MissionTag), Identifier.Empty);
25  MissionType = element.GetAttributeEnum(nameof(MissionType), MissionType.None);
26  if (MissionIdentifier.IsEmpty && MissionTag.IsEmpty && MissionType == MissionType.None)
27  {
28  DebugConsole.ThrowError($"Error in traitor event \"{prefab.Identifier}\". Mission requirement with no {nameof(MissionIdentifier)}, {nameof(MissionTag)} or {nameof(MissionType)}.",
29  contentPackage: prefab.ContentPackage);
30  }
31  }
32 
33  public bool Match(Mission mission)
34  {
35  if (mission == null) { return MissionIdentifier.IsEmpty && MissionTag.IsEmpty && MissionType == MissionType.None; }
36  if (!MissionIdentifier.IsEmpty)
37  {
38  return mission.Prefab.Identifier == MissionIdentifier;
39  }
40  else if (!MissionTag.IsEmpty)
41  {
42  return mission.Prefab.Tags.Contains(MissionTag);
43  }
44  else if (MissionType != MissionType.None)
45  {
46  return mission.Prefab.Type == MissionType;
47  }
48  return false;
49  }
50  }
51 
52  class LevelRequirement
53  {
54  private enum LevelType
55  {
57  Outpost,
58  Any
59  }
60 
61  private readonly LevelType levelType;
62  public ImmutableArray<Identifier> LocationTypes { get; }
63 
67  private readonly float minDifficulty;
71  private readonly float minDifficultyInCampaign;
72 
73  //feels a little weird to have something this specific here, but couldn't think of a better way to implement this
74  public ImmutableArray<PropertyConditional> RequiredItemConditionals;
75 
76  public LevelRequirement(ContentXElement element, TraitorEventPrefab prefab)
77  {
78  levelType = element.GetAttributeEnum(nameof(LevelType), LevelType.Any);
79  LocationTypes = element.GetAttributeIdentifierArray("locationtype", Array.Empty<Identifier>()).ToImmutableArray();
80  minDifficulty = element.GetAttributeFloat(nameof(minDifficulty), 0.0f);
81  minDifficultyInCampaign = element.GetAttributeFloat(nameof(minDifficultyInCampaign), Math.Max(minDifficulty, 5.0f));
82  List<PropertyConditional> requiredItemConditionals = new List<PropertyConditional>();
83  foreach (var subElement in element.Elements())
84  {
85  if (subElement.NameAsIdentifier() == "itemconditional")
86  {
87  requiredItemConditionals.AddRange(PropertyConditional.FromXElement(subElement));
88  }
89  }
90  RequiredItemConditionals = requiredItemConditionals.ToImmutableArray();
91  }
92 
93  public bool Match(Level level)
94  {
95  if (level?.LevelData == null) { return false; }
96  switch (levelType)
97  {
98  case LevelType.LocationConnection:
99  if (level.LevelData.Type != LevelData.LevelType.LocationConnection) { return false; }
100  break;
101  case LevelType.Outpost:
102  if (level.LevelData.Type != LevelData.LevelType.Outpost) { return false; }
103  break;
104  }
105  if (GameMain.GameSession?.Campaign != null)
106  {
107  if (level.Difficulty < minDifficultyInCampaign) { return false; }
108  }
109  else
110  {
111  if (level.Difficulty < minDifficulty) { return false; }
112  }
113  if (level.StartLocation == null)
114  {
115  if (LocationTypes.Any()) { return false; }
116  }
117  else
118  {
119  if (LocationTypes.Any() && !LocationTypes.Contains(level.StartLocation.Type.Identifier)) { return false; }
120  }
121  if (RequiredItemConditionals.Any())
122  {
123  bool matchFound = false;
124  foreach (var item in Item.ItemList)
125  {
126  if (RequiredItemConditionals.All(c => item.ConditionalMatches(c)))
127  {
128  matchFound = true;
129  break;
130  }
131  }
132  if (!matchFound) { return false; }
133  }
134  return true;
135  }
136  }
137 
138  class ReputationRequirement
139  {
140  public Identifier Faction;
141  public Identifier CompareToFaction;
142  public float CompareToValue;
143 
144  public readonly PropertyConditional.ComparisonOperatorType Operator;
145 
146  public ReputationRequirement(XElement element, TraitorEventPrefab prefab)
147  {
148  Faction = element.GetAttributeIdentifier(nameof(Faction), Identifier.Empty);
149 
150  string conditionStr = element.GetAttributeString("reputation", string.Empty);
151 
152  string[] splitString = conditionStr.Split(' ');
153  string value;
154  if (splitString.Length > 0)
155  {
156  //the first part of the string is the operator, skip it
157  value = string.Join(" ", splitString.Skip(1));
158  }
159  else
160  {
161  DebugConsole.ThrowError(
162  $"{conditionStr} in {prefab.Identifier} is too short."+
163  "It should start with an operator followed by a faction identifier or a floating point value.");
164  return;
165  }
166  Operator = PropertyConditional.GetComparisonOperatorType(splitString[0]);
167 
168  if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var floatVal))
169  {
170  CompareToValue = floatVal;
171  }
172  else
173  {
174  CompareToFaction = value.ToIdentifier();
175  }
176  }
177 
178  public bool Match(CampaignMode campaign)
179  {
180  var faction1 = campaign.GetFaction(Faction);
181  if (faction1 == null)
182  {
183  DebugConsole.ThrowError($"Could not find the faction {Faction}.");
184  return false;
185  }
186  if (!CompareToFaction.IsEmpty)
187  {
188  var faction2 = campaign.GetFaction(Faction);
189  if (faction2 == null)
190  {
191  DebugConsole.ThrowError($"Could not find the faction {CompareToFaction}.");
192  return false;
193  }
194  return PropertyConditional.CompareFloat(faction1.Reputation.Value, faction2.Reputation.Value, Operator);
195  }
196  return PropertyConditional.CompareFloat(faction1.Reputation.Value, CompareToValue, Operator);
197  }
198  }
199 
200  public readonly Sprite? Icon;
201  public readonly Color IconColor;
202 
203  public const int MinDangerLevel = 1;
204  public const int MaxDangerLevel = 3;
205 
206  public ImmutableHashSet<Identifier> Tags;
207 
208  private readonly ImmutableArray<ReputationRequirement> reputationRequirements;
209  private readonly ImmutableArray<MissionRequirement> missionRequirements;
210  private readonly ImmutableArray<LevelRequirement> levelRequirements;
211  public bool HasReputationRequirements => reputationRequirements.Any();
212  public bool HasMissionRequirements => missionRequirements.Any();
213  public bool HasLevelRequirements => levelRequirements.Any();
214 
218  public ImmutableHashSet<Identifier> RequiredCompletedTags;
219 
220  public readonly int DangerLevel;
221 
226  public readonly int RequiredPreviousDangerLevel;
227 
233 
237  public readonly int MinPlayerCount;
238 
243  public readonly int SecondaryTraitorAmount;
244 
248  public readonly float SecondaryTraitorPercentage;
249 
253  public readonly bool AllowAccusingSecondaryTraitor;
254 
259 
263  public readonly bool IsChainable;
264 
265  public readonly float StealPercentageOfExperience;
266 
267  public TraitorEventPrefab(ContentXElement element, RandomEventsFile file, Identifier fallbackIdentifier = default)
268  : base(element, file, fallbackIdentifier)
269  {
270  DangerLevel = MathHelper.Clamp(element.GetAttributeInt(nameof(DangerLevel), MinDangerLevel), min: MinDangerLevel, max: MaxDangerLevel);
271 
272  RequiredPreviousDangerLevel = MathHelper.Clamp(element.GetAttributeInt(nameof(RequiredPreviousDangerLevel), def: DangerLevel - 1), min: 0, max: MaxDangerLevel - 1);
274 
275  MinPlayerCount = element.GetAttributeInt(nameof(MinPlayerCount), 0);
276 
279 
281 
283 
284  Tags = element.GetAttributeIdentifierImmutableHashSet(nameof(Tags), ImmutableHashSet<Identifier>.Empty);
285  RequiredCompletedTags = element.GetAttributeIdentifierImmutableHashSet(nameof(RequiredCompletedTags), ImmutableHashSet<Identifier>.Empty);
286 
288 
289  IsChainable = element.GetAttributeBool(nameof(IsChainable), true);
290 
291  List<ReputationRequirement> reputationRequirements = new List<ReputationRequirement>();
292  List<LevelRequirement> levelRequirements = new List<LevelRequirement>();
293  List<MissionRequirement> missionRequirements = new List<MissionRequirement>();
294  foreach (var subElement in element.Elements())
295  {
296  switch (subElement.Name.ToString().ToLowerInvariant())
297  {
298  case "reputationrequirement":
299  reputationRequirements.Add(new ReputationRequirement(subElement!, this));
300  break;
301  case "missionrequirement":
302  missionRequirements.Add(new MissionRequirement(subElement!, this));
303  break;
304  case "levelrequirement":
305  levelRequirements.Add(new LevelRequirement(subElement!, this));
306  break;
307  case "icon":
308  Icon = new Sprite(subElement);
309  IconColor = subElement.GetAttributeColor("color", Color.White);
310  break;
311  }
312  }
313  this.reputationRequirements = reputationRequirements.ToImmutableArray();
314  this.levelRequirements = levelRequirements.ToImmutableArray();
315  this.missionRequirements = missionRequirements.ToImmutableArray();
316  }
317 
319  {
320  if (campaign == null)
321  {
322  //no requirements in the campaign
323  return true;
324  }
325  foreach (ReputationRequirement requirement in reputationRequirements)
326  {
327  if (!requirement.Match(campaign)) { return false; }
328  }
329  return true;
330  }
331  public bool MissionRequirementsMet(GameSession? gameSession)
332  {
333  if (gameSession == null) { return false; }
334  foreach (MissionRequirement requirement in missionRequirements)
335  {
336  if (gameSession.Missions.None(m => requirement.Match(m))) { return false; }
337  }
338  return true;
339  }
340  public bool LevelRequirementsMet(Level? level)
341  {
342  if (level == null) { return false; }
343  //by default (if no requirements are specified) traitor events happen in LocationConnections.
344  if (levelRequirements.None() && level.Type != LevelData.LevelType.LocationConnection)
345  {
346  return false;
347  }
348  foreach (LevelRequirement requirement in levelRequirements)
349  {
350  if (!requirement.Match(level)) { return false; }
351  }
352  return true;
353  }
354 
355  public override void Dispose()
356  {
357  Icon?.Remove();
358  }
359 
360  public override string ToString()
361  {
362  return $"{nameof(TraitorEventPrefab)} ({Identifier})";
363  }
364 
365  }
366 }
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
float GetAttributeFloat(string key, float def)
IEnumerable< ContentXElement > Elements()
bool GetAttributeBool(string key, bool def)
int GetAttributeInt(string key, int def)
ImmutableHashSet< Identifier > GetAttributeIdentifierImmutableHashSet(string key, ImmutableHashSet< Identifier >? def, bool trim=true)
readonly Identifier Faction
If set, the event set can only be chosen in locations that belong to this faction.
Definition: EventPrefab.cs:43
static GameSession?? GameSession
Definition: GameMain.cs:88
static readonly List< Item > ItemList
readonly LevelType Type
Definition: LevelData.cs:19
LocationType Type
Definition: Location.cs:91
ContentPackage? ContentPackage
Definition: Prefab.cs:37
readonly Identifier Identifier
Definition: Prefab.cs:34
Conditionals are used by some in-game mechanics to require one or more conditions to be met for those...
static ComparisonOperatorType GetComparisonOperatorType(string op)
ComparisonOperatorType
There are several ways to compare properties to values. The comparison operator to use can be specifi...
static bool CompareFloat(float val1, float val2, ComparisonOperatorType op)
static IEnumerable< PropertyConditional > FromXElement(ContentXElement element, Predicate< XAttribute >? predicate=null)
readonly int RequiredPreviousDangerLevel
An event of this danger level (or higher) must have been selected previously for this event to trigge...
bool ReputationRequirementsMet(CampaignMode? campaign)
readonly int MoneyPenaltyForUnfoundedTraitorAccusation
Money penalty if the crew votes a wrong player as the traitor
bool MissionRequirementsMet(GameSession? gameSession)
bool LevelRequirementsMet(Level? level)
readonly bool RequirePreviousDangerLevelCompleted
An event of a lower danger level must have been completed on the previous round for this event to tri...
readonly int MinPlayerCount
Minimum number of non-spectating human players on the server for the event to get selected.
readonly bool AllowAccusingSecondaryTraitor
Does accusing a secondary traitor count as correctly identifying the traitor?
TraitorEventPrefab(ContentXElement element, RandomEventsFile file, Identifier fallbackIdentifier=default)
readonly int SecondaryTraitorAmount
Number of players to assign as a "secondary traitor". If both this and SecondaryTraitorPercentage are...
readonly bool IsChainable
Is this event chainable, i.e. does the same traitor get another, higher-lvl one if they complete this...
ImmutableHashSet< Identifier > Tags
readonly float SecondaryTraitorPercentage
Percentage of players to assign as a "secondary traitor".
ImmutableHashSet< Identifier > RequiredCompletedTags
An event with one of these tags must've been completed previously for this event to trigger.