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 Identifier 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.GetAttributeIdentifier(nameof(MissionType), Identifier.Empty);
26  if (MissionIdentifier.IsEmpty && MissionTag.IsEmpty && MissionType == Identifier.Empty)
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 == Identifier.Empty; }
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.IsEmpty)
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  private readonly ImmutableArray<ReputationRequirement> reputationRequirements;
207  private readonly ImmutableArray<MissionRequirement> missionRequirements;
208  private readonly ImmutableArray<LevelRequirement> levelRequirements;
209  public bool HasReputationRequirements => reputationRequirements.Any();
210  public bool HasMissionRequirements => missionRequirements.Any();
211  public bool HasLevelRequirements => levelRequirements.Any();
212 
216  public ImmutableHashSet<Identifier> RequiredCompletedTags;
217 
218  public readonly int DangerLevel;
219 
224  public readonly int RequiredPreviousDangerLevel;
225 
231 
235  public readonly int MinPlayerCount;
236 
241  public readonly int SecondaryTraitorAmount;
242 
246  public readonly float SecondaryTraitorPercentage;
247 
251  public readonly bool AllowAccusingSecondaryTraitor;
252 
257 
261  public readonly bool IsChainable;
262 
263  public readonly float StealPercentageOfExperience;
264 
265  public TraitorEventPrefab(ContentXElement element, RandomEventsFile file, Identifier fallbackIdentifier = default)
266  : base(element, file, fallbackIdentifier)
267  {
268  DangerLevel = MathHelper.Clamp(element.GetAttributeInt(nameof(DangerLevel), MinDangerLevel), min: MinDangerLevel, max: MaxDangerLevel);
269 
270  RequiredPreviousDangerLevel = MathHelper.Clamp(element.GetAttributeInt(nameof(RequiredPreviousDangerLevel), def: DangerLevel - 1), min: 0, max: MaxDangerLevel - 1);
272 
273  MinPlayerCount = element.GetAttributeInt(nameof(MinPlayerCount), 0);
274 
277 
279 
281 
282  RequiredCompletedTags = element.GetAttributeIdentifierImmutableHashSet(nameof(RequiredCompletedTags), ImmutableHashSet<Identifier>.Empty);
283 
285 
286  IsChainable = element.GetAttributeBool(nameof(IsChainable), true);
287 
288  List<ReputationRequirement> reputationRequirements = new List<ReputationRequirement>();
289  List<LevelRequirement> levelRequirements = new List<LevelRequirement>();
290  List<MissionRequirement> missionRequirements = new List<MissionRequirement>();
291  foreach (var subElement in element.Elements())
292  {
293  switch (subElement.Name.ToString().ToLowerInvariant())
294  {
295  case "reputationrequirement":
296  reputationRequirements.Add(new ReputationRequirement(subElement!, this));
297  break;
298  case "missionrequirement":
299  missionRequirements.Add(new MissionRequirement(subElement!, this));
300  break;
301  case "levelrequirement":
302  levelRequirements.Add(new LevelRequirement(subElement!, this));
303  break;
304  case "icon":
305  Icon = new Sprite(subElement);
306  IconColor = subElement.GetAttributeColor("color", Color.White);
307  break;
308  }
309  }
310  this.reputationRequirements = reputationRequirements.ToImmutableArray();
311  this.levelRequirements = levelRequirements.ToImmutableArray();
312  this.missionRequirements = missionRequirements.ToImmutableArray();
313  }
314 
316  {
317  if (campaign == null)
318  {
319  //no requirements in the campaign
320  return true;
321  }
322  foreach (ReputationRequirement requirement in reputationRequirements)
323  {
324  if (!requirement.Match(campaign)) { return false; }
325  }
326  return true;
327  }
328  public bool MissionRequirementsMet(GameSession? gameSession)
329  {
330  if (gameSession == null) { return false; }
331  foreach (MissionRequirement requirement in missionRequirements)
332  {
333  if (gameSession.Missions.None(m => requirement.Match(m))) { return false; }
334  }
335  return true;
336  }
337  public bool LevelRequirementsMet(Level? level)
338  {
339  if (level == null) { return false; }
340  //by default (if no requirements are specified) traitor events happen in LocationConnections.
341  if (levelRequirements.None() && level.Type != LevelData.LevelType.LocationConnection)
342  {
343  return false;
344  }
345  foreach (LevelRequirement requirement in levelRequirements)
346  {
347  if (!requirement.Match(level)) { return false; }
348  }
349  return true;
350  }
351 
352  public override void Dispose()
353  {
354  Icon?.Remove();
355  }
356 
357  public override string ToString()
358  {
359  return $"{nameof(TraitorEventPrefab)} ({Identifier})";
360  }
361 
362  }
363 }
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:53
static GameSession?? GameSession
Definition: GameMain.cs:88
static readonly List< Item > ItemList
readonly LevelType Type
Definition: LevelData.cs:20
LocationType Type
Definition: Location.cs:94
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...
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.