3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
16 private readonly List<Character> characters =
new List<Character>();
17 private readonly Dictionary<Character, List<Item>> characterItems =
new Dictionary<Character, List<Item>>();
18 private readonly Dictionary<HumanPrefab, List<StatusEffect>> characterStatusEffects =
new Dictionary<HumanPrefab, List<StatusEffect>>();
20 private readonly
int baseEscortedCharacters;
21 private readonly
float scalingEscortedCharacters;
22 private readonly
float terroristChance;
24 private int calculatedReward;
29 private readonly List<Character> terroristCharacters =
new List<Character>();
30 private bool terroristsShouldAct =
false;
31 private float terroristDistanceSquared;
32 private const string TerroristTeamChangeIdentifier =
"terrorist";
33 private readonly
string terroristAnnounceDialogTag =
string.Empty;
36 : base(prefab, locations, sub)
48 private void CalculateReward()
50 if (missionSub ==
null)
52 calculatedReward =
Prefab.Reward;
60 string rewardText = $
"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}
", GetReward(missionSub))}‖end‖";
66 if (sub != missionSub)
71 return calculatedReward;
74 int CalculateScalingEscortedCharacterCount(
bool inMission =
false)
76 if (missionSub ==
null || missionSub.
Info ==
null)
80 DebugConsole.ThrowError(
"MainSub was null when trying to retrieve submarine size for determining escorted character count!",
85 return (
int)Math.Round(baseEscortedCharacters + scalingEscortedCharacters * (missionSub.
Info.
RecommendedCrewSizeMin + missionSub.
Info.RecommendedCrewSizeMax) / 2);
88 private void InitEscort()
91 characterItems.Clear();
93 WayPoint explicitStayInHullPos = WayPoint.GetRandom(
SpawnType.Human,
null,
Submarine.MainSub);
94 Rand.RandSync randSync = Rand.RandSync.ServerAndClient;
96 if (terroristChance > 0f)
99 randSync = Rand.RandSync.Unsynced;
102 List<HumanPrefab> humanPrefabsToSpawn =
new List<HumanPrefab>();
103 foreach (ContentXElement characterElement
in characterConfig.Elements())
105 int count = CalculateScalingEscortedCharacterCount(inMission:
true);
107 for (
int i = 0; i < count; i++)
109 humanPrefabsToSpawn.Add(humanPrefab);
111 foreach (var element
in characterElement.Elements())
113 if (element.NameAsIdentifier() ==
"statuseffect")
115 var newEffect = StatusEffect.Load(element, parentDebugName:
Prefab.
Name.
Value);
116 if (newEffect ==
null) {
continue; }
117 if (!characterStatusEffects.ContainsKey(humanPrefab))
119 characterStatusEffects[humanPrefab] =
new List<StatusEffect> { newEffect };
123 characterStatusEffects[humanPrefab].Add(newEffect);
130 foreach (var humanPrefab
in humanPrefabsToSpawn)
132 if (humanPrefab ==
null || humanPrefab.Job.IsEmpty || humanPrefab.Job ==
"any") {
continue; }
133 var jobPrefab = humanPrefab.GetJobPrefab(randSync);
134 if (jobPrefab !=
null)
136 var jobSpecificSpawnPos = WayPoint.GetRandom(
SpawnType.Human, jobPrefab,
Submarine.MainSub);
137 if (jobSpecificSpawnPos !=
null)
139 explicitStayInHullPos = jobSpecificSpawnPos;
144 foreach (var humanPrefab
in humanPrefabsToSpawn)
147 if (spawnedCharacter.AIController is HumanAIController humanAI)
149 humanAI.InitMentalStateManager();
151 if (characterStatusEffects.TryGetValue(humanPrefab, out var statusEffectList))
153 foreach (var statusEffect
in statusEffectList)
155 statusEffect.Apply(statusEffect.type, 1.0f, spawnedCharacter, spawnedCharacter);
161 if (terroristChance > 0f)
163 int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
164 terroristCount = Math.Clamp(terroristCount, 1, characters.Count);
166 terroristCharacters.Clear();
167 characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c));
168 terroristCharacters.ForEach(c => c.IsHostileEscortee =
true);
169 terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f);
171 DebugConsole.AddWarning(
"Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared));
172 foreach (Character character
in terroristCharacters)
174 DebugConsole.AddWarning(character.Name +
" is a terrorist.");
180 private void InitCharacters()
182 int scalingCharacterCount = CalculateScalingEscortedCharacterCount(inMission:
true);
184 if (scalingCharacterCount * characterConfig.Elements().Count() != characters.Count)
186 DebugConsole.AddWarning(
"Character count did not match expected character count in InitCharacters of EscortMission",
192 foreach (ContentXElement element
in characterConfig.Elements())
194 string escortIdentifier = element.GetAttributeString(
"escortidentifier",
string.Empty);
195 string colorIdentifier = element.GetAttributeString(
"color",
string.Empty);
196 for (
int k = 0; k < scalingCharacterCount; k++)
199 characters[k + i].IsEscorted =
true;
200 if (escortIdentifier !=
string.Empty)
202 if (escortIdentifier ==
"vip")
204 vipCharacter = characters[k + i];
207 characters[k + i].
UniqueNameColor = element.GetAttributeColor(
"color", Color.LightGreen);
215 if (characters.Count > 0)
218 throw new Exception($
"characters.Count > 0 ({characters.Count})");
220 DebugConsole.AddWarning(
"Character list was not empty at the start of a escort mission. The mission instance may not have been ended correctly on previous rounds.");
225 if (characterConfig ==
null)
227 DebugConsole.ThrowError(
"Failed to initialize characters for escort mission (characterConfig == null)",
233 if (missionSub ==
null)
246 void TryToTriggerTerrorists()
248 if (terroristsShouldAct)
251 foreach (
Character character
in terroristCharacters)
262 character.
TryAddNewTeamChange(TerroristTeamChangeIdentifier,
new ActiveTeamChange(
CharacterTeamType.None, ActiveTeamChange.TeamChangePriorities.Willful, aggressiveBehavior:
true));
263 if (!
string.IsNullOrEmpty(terroristAnnounceDialogTag))
265 character.
Speak(TextManager.Get(
"dialogterroristannounce").Value,
null, Rand.Range(0.5f, 3f));
267 ContentXElement randomElement = itemConfig.Elements().GetRandomUnsynced(e => e.GetAttributeFloat(0f,
"mindifficulty") <= Level.Loaded.Difficulty);
268 if (randomElement !=
null)
270 HumanPrefab.InitializeItem(character, randomElement, character.
Submarine, humanPrefab:
null, createNetworkEvents:
true);
275 else if (Vector2.DistanceSquared(
Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) < terroristDistanceSquared)
277 foreach (Character character
in terroristCharacters)
279 if (character.AIController is HumanAIController humanAI)
281 humanAI.ObjectiveManager.AddObjective(
new AIObjectiveEscapeHandcuffs(character, humanAI.ObjectiveManager, shouldSwitchTeams:
false, beginInstantly:
true));
284 terroristsShouldAct =
true;
288 bool NonTerroristsStillAlive(IEnumerable<Character> characterList)
290 return characterList.All(c => terroristCharacters.Contains(c) || IsAlive(c));
297 int newState =
State;
298 TryToTriggerTerrorists();
302 if (!NonTerroristsStillAlive(characters))
306 if (terroristCharacters.Any() && terroristCharacters.All(c => !IsAlive(c)))
314 if (!NonTerroristsStillAlive(characters))
324 private static bool Survived(
Character character)
330 private static bool IsAlive(Character character)
332 return character !=
null && !character.
Removed && !character.IsDead;
339 bool friendliesSurvived = characters.Except(terroristCharacters).All(c => Survived(c));
340 bool vipDied =
false;
343 if (vipCharacter !=
null)
345 vipDied = !Survived(vipCharacter);
348 if (friendliesSurvived && !vipDied)
360 foreach (
Character character
in characters)
362 if (character.
Inventory ==
null) {
continue; }
366 if (!characterItems.Any(c => c.Value.Contains(item)))
368 item.
Drop(character);
375 foreach (var characterItem
in characterItems)
377 if (Survived(characterItem.Key) || !completed)
379 foreach (
Item item
in characterItem.Value)
391 characterItems.Clear();
bool HasTeamChange(string identifier)
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
CharacterInventory Inventory
bool IsHostileEscortee
Set true only, if the character is turned hostile from an escort mission (See EscortMission).
bool TryAddNewTeamChange(string identifier, ActiveTeamChange newTeamChange)
string? GetAttributeString(string key, string? def)
float GetAttributeFloat(string key, float def)
ContentXElement? GetChildElement(string name)
int GetAttributeInt(string key, int def)
override void UpdateMissionSpecific(float deltaTime)
override void StartMissionSpecific(Level level)
override bool DetermineCompleted()
EscortMission(MissionPrefab prefab, Location[] locations, Submarine sub)
override void EndMissionSpecific(bool completed)
override int GetBaseReward(Submarine sub)
Calculates the base reward, can be overridden for different mission types
IEnumerable< Item > AllItemsMod
All items contained in the inventory. Allows modifying the contents of the inventory while being enum...
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison=StringComparison.Ordinal)
readonly MissionPrefab Prefab
static Character CreateHuman(HumanPrefab humanPrefab, List< Character > characters, Dictionary< Character, List< Item >> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn=null, Rand.RandSync humanPrefabRandSync=Rand.RandSync.ServerAndClient)
HumanPrefab GetHumanPrefabFromElement(XElement element)
LocalizedString descriptionWithoutReward
LocalizedString description
readonly LocalizedString Name
readonly ContentXElement ConfigElement
ContentPackage? ContentPackage
IEnumerable< Submarine > DockedTo
int RecommendedCrewSizeMin