3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Globalization;
15 class MissionRequirement
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)
28 DebugConsole.ThrowError($
"Error in traitor event \"{prefab.Identifier}\". Mission requirement with no {nameof(MissionIdentifier)}, {nameof(MissionTag)} or {nameof(MissionType)}.",
33 public bool Match(
Mission mission)
35 if (mission ==
null) {
return MissionIdentifier.IsEmpty && MissionTag.IsEmpty && MissionType ==
Identifier.Empty; }
36 if (!MissionIdentifier.IsEmpty)
40 else if (!MissionTag.IsEmpty)
42 return mission.
Prefab.
Tags.Contains(MissionTag);
44 else if (!MissionType.IsEmpty)
52 class LevelRequirement
54 private enum LevelType
61 private readonly LevelType levelType;
62 public ImmutableArray<Identifier> LocationTypes {
get; }
67 private readonly
float minDifficulty;
71 private readonly
float minDifficultyInCampaign;
74 public ImmutableArray<PropertyConditional> RequiredItemConditionals;
78 levelType = element.GetAttributeEnum(nameof(LevelType), LevelType.Any);
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())
85 if (subElement.NameAsIdentifier() ==
"itemconditional")
90 RequiredItemConditionals = requiredItemConditionals.ToImmutableArray();
93 public bool Match(
Level level)
95 if (level?.
LevelData ==
null) {
return false; }
98 case LevelType.LocationConnection:
101 case LevelType.Outpost:
107 if (level.
Difficulty < minDifficultyInCampaign) {
return false; }
111 if (level.
Difficulty < minDifficulty) {
return false; }
115 if (LocationTypes.Any()) {
return false; }
121 if (RequiredItemConditionals.Any())
123 bool matchFound =
false;
126 if (RequiredItemConditionals.All(c => item.ConditionalMatches(c)))
132 if (!matchFound) {
return false; }
138 class ReputationRequirement
142 public float CompareToValue;
150 string conditionStr = element.GetAttributeString(
"reputation",
string.Empty);
152 string[] splitString = conditionStr.Split(
' ');
154 if (splitString.Length > 0)
157 value =
string.Join(
" ", splitString.Skip(1));
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.");
168 if (
float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var floatVal))
170 CompareToValue = floatVal;
174 CompareToFaction = value.ToIdentifier();
181 if (faction1 ==
null)
183 DebugConsole.ThrowError($
"Could not find the faction {Faction}.");
186 if (!CompareToFaction.IsEmpty)
189 if (faction2 ==
null)
191 DebugConsole.ThrowError($
"Could not find the faction {CompareToFaction}.");
206 private readonly ImmutableArray<ReputationRequirement> reputationRequirements;
207 private readonly ImmutableArray<MissionRequirement> missionRequirements;
208 private readonly ImmutableArray<LevelRequirement> levelRequirements;
266 : base(element, file, fallbackIdentifier)
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())
293 switch (subElement.Name.ToString().ToLowerInvariant())
295 case "reputationrequirement":
296 reputationRequirements.Add(
new ReputationRequirement(subElement!,
this));
298 case "missionrequirement":
299 missionRequirements.Add(
new MissionRequirement(subElement!,
this));
301 case "levelrequirement":
302 levelRequirements.Add(
new LevelRequirement(subElement!,
this));
306 IconColor = subElement.GetAttributeColor(
"color", Color.White);
310 this.reputationRequirements = reputationRequirements.ToImmutableArray();
311 this.levelRequirements = levelRequirements.ToImmutableArray();
312 this.missionRequirements = missionRequirements.ToImmutableArray();
317 if (campaign ==
null)
322 foreach (ReputationRequirement requirement
in reputationRequirements)
324 if (!requirement.Match(campaign)) {
return false; }
330 if (gameSession ==
null) {
return false; }
331 foreach (MissionRequirement requirement
in missionRequirements)
333 if (gameSession.Missions.None(m => requirement.Match(m))) {
return false; }
339 if (level ==
null) {
return false; }
345 foreach (LevelRequirement requirement
in levelRequirements)
347 if (!requirement.Match(level)) {
return false; }
359 return $
"{nameof(TraitorEventPrefab)} ({Identifier})";
Faction GetFaction(Identifier identifier)
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.
static GameSession?? GameSession
static readonly List< Item > ItemList
readonly LevelData LevelData
readonly MissionPrefab Prefab
readonly ImmutableHashSet< Identifier > Tags
ContentPackage? ContentPackage
readonly Identifier Identifier
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
readonly float StealPercentageOfExperience
bool HasLevelRequirements
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.
bool HasReputationRequirements
readonly bool AllowAccusingSecondaryTraitor
Does accusing a secondary traitor count as correctly identifying the traitor?
TraitorEventPrefab(ContentXElement element, RandomEventsFile file, Identifier fallbackIdentifier=default)
bool HasMissionRequirements
override string ToString()
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.