5 using Microsoft.Xna.Framework;
7 using System.Collections.Generic;
20 const float ConversationIntervalMin = 100.0f;
21 const float ConversationIntervalMax = 180.0f;
22 const float ConversationIntervalMultiplierMultiplayer = 5.0f;
23 private float conversationTimer, conversationLineTimer;
24 private readonly List<(
Character speaker,
string line)> pendingConversationLines =
new List<(
Character speaker,
string line)>();
28 private readonly List<CharacterInfo> characterInfos =
new List<CharacterInfo>();
29 private readonly List<Character> characters =
new List<Character>();
41 return characterInfos;
58 public List<ActiveOrder>
ActiveOrders {
get; } =
new List<ActiveOrder>();
66 conversationTimer = 5.0f;
67 InitProjectSpecific();
70 partial
void InitProjectSpecific();
76 string message = $
"Attempted to add a \"{order.Name}\" order with no target entity to CrewManager!\n{Environment.StackTrace.CleanupStackTrace()}";
77 DebugConsole.AddWarning(message);
78 GameAnalyticsManager.AddErrorEventOnce(
"CrewManager.AddOrder:OrderTargetEntityNull", GameAnalyticsManager.ErrorSeverity.Error, message);
83 var isUnignoreOrder = order.
Identifier == Tags.UnignoreThis;
86 o.Order.Prefab == orderPrefab && MatchesTarget(o.Order.TargetEntity, order.
TargetEntity) &&
89 if (existingOrder !=
null)
102 else if (!isUnignoreOrder)
110 foreach (var stackedItem
in item.GetStackedItems())
115 HintManager.OnItemMarkedForDeconstruction(order.
OrderGiver);
120 foreach (var stackedItem
in item.GetStackedItems())
129 HintManager.OnActiveOrderAdded(order);
134 static bool MatchesTarget(
Entity existingTarget,
Entity newTarget)
136 if (existingTarget == newTarget) {
return true; }
137 if (existingTarget is
Hull existingHullTarget && newTarget is
Hull newHullTarget)
139 return existingHullTarget.linkedTo.Contains(newHullTarget);
149 foreach (var characterElement
in element.Elements())
151 if (!characterElement.Name.ToString().Equals(
"character", StringComparison.OrdinalIgnoreCase)) {
continue; }
154 if (characterElement.GetAttributeBool(
"lastcontrolled",
false)) { characterInfo.
LastControlled =
true; }
155 characterInfo.
CrewListIndex = characterElement.GetAttributeInt(
"crewlistindex", -1);
157 characterInfos.Add(characterInfo);
158 foreach (var subElement
in characterElement.Elements())
160 switch (subElement.Name.ToString().ToLowerInvariant())
163 characterInfo.InventoryData = subElement;
166 characterInfo.HealthData = subElement;
169 characterInfo.OrderData = subElement;
182 characterInfos.Remove(characterInfo);
189 DebugConsole.ThrowError(
"Tried to add a removed character to CrewManager!\n" + Environment.StackTrace.CleanupStackTrace());
194 DebugConsole.ThrowError(
"Tried to add a dead character to CrewManager!\n" + Environment.StackTrace.CleanupStackTrace());
197 if (character.
Info ==
null)
201 DebugConsole.ThrowError($
"Added a character with no {nameof(CharacterInfo)} to the crew." + Environment.StackTrace.CleanupStackTrace());
205 DebugConsole.ThrowError($
"Added add a character with no {nameof(CharacterInfo)} to the crew. This may lead to issues: consider adding {nameof(CharacterPrefab.HasCharacterInfo)}=\"True\" to the character config.");
209 if (!characters.Contains(character))
211 characters.Add(character);
213 if (!characterInfos.Contains(character.
Info))
215 characterInfos.Add(character.
Info);
233 var idleObjective = humanAI.ObjectiveManager.GetObjective<
AIObjectiveIdle>();
234 if (idleObjective !=
null)
253 if (character ==
null)
255 DebugConsole.ThrowError(
"Tried to remove a null character from CrewManager.\n" + Environment.StackTrace.CleanupStackTrace());
258 characters.Remove(character);
261 characterInfos.Remove(character.
Info);
267 if (resetCrewListIndex)
269 ResetCrewListIndex(character);
276 if (characterInfos.Contains(characterInfo))
278 DebugConsole.ThrowError(
"Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace.CleanupStackTrace());
282 characterInfos.Add(characterInfo);
287 characterInfos.Clear();
293 GUIContextMenu.CurrentContextMenu =
null;
298 List<WayPoint> spawnWaypoints =
null;
304 while (spawnWaypoints.Count > characterInfos.Count)
306 spawnWaypoints.RemoveAt(Rand.Int(spawnWaypoints.Count));
308 while (spawnWaypoints.Any() && spawnWaypoints.Count < characterInfos.Count)
310 spawnWaypoints.Add(spawnWaypoints[Rand.Int(spawnWaypoints.Count)]);
313 if (spawnWaypoints ==
null || !spawnWaypoints.Any())
315 spawnWaypoints = mainSubWaypoints;
318 System.Diagnostics.Debug.Assert(spawnWaypoints.Count == mainSubWaypoints.Count);
320 for (
int i = 0; i < spawnWaypoints.Count; i++)
322 var info = characterInfos[i];
338 conversationTimer =
IsSinglePlayer ? Rand.Range(5.0f, 10.0f) : Rand.Range(45.0f, 60.0f);
349 wp.CurrentHull !=
null &&
350 wp.CurrentHull.OutpostModuleTags.Contains(
"airlock".ToIdentifier()));
355 if (character.
Info !=
null)
359 DebugConsole.AddWarning($
"Error when initializing a round: character \"{character.Name}\" has not been given their initial items but has saved inventory data. Using the saved inventory data instead of giving the character new items.");
372 var idCard = item.GetComponent<Items.
Components.IdCard>();
375 idCard.SubmarineSpecificID = 0;
398 characterInfo.
Rename(newName);
399 RenameCharacterProjSpecific(characterInfo);
402 partial
void RenameCharacterProjSpecific(
CharacterInfo characterInfo);
411 foreach (var characterInfo
in characterInfos)
413 characterInfo?.ClearCurrentOrders();
423 ActiveOrders.RemoveAll(o => (o.FadeOutTime.HasValue && o.FadeOutTime <= 0.0f) ||
424 (o.Order.TargetEntity !=
null && o.Order.TargetEntity.Removed));
426 UpdateConversations(deltaTime);
427 UpdateProjectSpecific(deltaTime);
439 if (conversationLines ==
null || conversationLines.Count == 0) {
return; }
440 pendingConversationLines.AddRange(conversationLines);
443 partial
void CreateRandomConversation();
445 private void UpdateConversations(
float deltaTime)
451 if (GameMain.NetworkMember !=
null && GameMain.NetworkMember.ServerSettings.DisableBotConversations) {
return; }
453 conversationTimer -= deltaTime;
454 if (conversationTimer <= 0.0f)
456 CreateRandomConversation();
457 conversationTimer = Rand.Range(ConversationIntervalMin, ConversationIntervalMax);
458 if (GameMain.NetworkMember !=
null)
460 conversationTimer *= ConversationIntervalMultiplierMultiplayer;
464 if (welcomeMessageNPC ==
null)
466 foreach (Character npc
in Character.CharacterList)
469 if (npc.AIController is HumanAIController humanAI && (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>() || humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()))
473 foreach (Character player
in Character.CharacterList)
475 if (player.TeamID != npc.TeamID && !player.IsIncapacitated && player.CurrentHull == npc.CurrentHull)
477 List<Character> availableSpeakers =
new List<Character>() { npc, player };
478 List<Identifier> dialogFlags =
new List<Identifier>() {
"OutpostNPC".ToIdentifier(),
"EnterOutpost".ToIdentifier() };
479 if (npc.HumanPrefab !=
null)
481 foreach (var tag
in npc.HumanPrefab.GetTags())
483 dialogFlags.Add(tag);
486 if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
488 if (campaignMode.Map?.CurrentLocation?.Type?.Identifier ==
"abandoned")
490 dialogFlags.Remove(
"OutpostNPC".ToIdentifier());
492 else if (campaignMode.Map?.CurrentLocation?.Reputation !=
null)
494 float normalizedReputation = MathUtils.InverseLerp(
495 campaignMode.Map.CurrentLocation.Reputation.MinReputation,
496 campaignMode.Map.CurrentLocation.Reputation.MaxReputation,
497 campaignMode.Map.CurrentLocation.Reputation.Value);
498 if (normalizedReputation < 0.2f)
500 dialogFlags.Add(
"LowReputation".ToIdentifier());
502 else if (normalizedReputation > 0.8f)
504 dialogFlags.Add(
"HighReputation".ToIdentifier());
508 pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers, dialogFlags));
509 welcomeMessageNPC = npc;
513 if (welcomeMessageNPC !=
null) {
break; }
516 else if (welcomeMessageNPC.
Removed)
518 welcomeMessageNPC =
null;
521 if (pendingConversationLines.Count > 0)
523 conversationLineTimer -= deltaTime;
524 if (conversationLineTimer <= 0.0f)
527 if (pendingConversationLines[0].speaker.SpeechImpediment >= 100.0f)
529 pendingConversationLines.Clear();
533 pendingConversationLines[0].speaker.Speak(pendingConversationLines[0].line,
null);
534 if (pendingConversationLines.Count > 1)
536 conversationLineTimer = MathHelper.Clamp(pendingConversationLines[0].line.Length * 0.1f, 1.0f, 5.0f);
538 pendingConversationLines.RemoveAt(0);
547 bool isControlledCharacterNull = controlledCharacter ==
null;
549 if (isControlledCharacterNull) {
return null; }
552 (isControlledCharacterNull || operatingCharacter.CanHearCharacter(controlledCharacter)))
554 return operatingCharacter;
556 return GetCharactersSortedForOrder(order, characters, controlledCharacter, includeSelf).FirstOrDefault(c => isControlledCharacterNull || c.CanHearCharacter(controlledCharacter)) ?? controlledCharacter;
561 var filteredCharacters = characters.Where(c => controlledCharacter ==
null || ((includeSelf || c != controlledCharacter) && c.TeamID == controlledCharacter.
TeamID));
562 if (extraCharacters !=
null)
564 filteredCharacters = filteredCharacters.Union(extraCharacters);
566 return filteredCharacters
572 && c.CurrentOrders.Any(o
579 .ThenByDescending(c => c.CurrentOrders.None(o => o !=
null && o.Identifier == order.
Identifier))
583 .ThenByDescending(c => c.IsBot)
590 partial
void UpdateProjectSpecific(
float deltaTime);
595 var ordersToSave =
new List<Order>();
598 var order = activeOrder?.
Order;
599 if (order ==
null || activeOrder.FadeOutTime.HasValue) {
continue; }
607 if (element ==
null) {
return; }
611 if (orderInfo.IsIgnoreOrder)
613 switch (orderInfo.TargetType)
616 ignoreTarget = orderInfo.TargetEntity as
IIgnorable;
619 ignoreTarget = s.GetSection(orderInfo.WallSectionIndex.Value);
622 DebugConsole.ThrowError(
"Error loading an ignore order - can't find a proper ignore target");
626 if (orderInfo.TargetEntity ==
null || (orderInfo.IsIgnoreOrder && ignoreTarget ==
null))
631 if (ignoreTarget !=
null)
float Priority
Final priority value after all calculations.
AIObjective CurrentObjective
Includes orders.
List< Order > CurrentOrders
virtual AIController AIController
static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, bool hasAi=true, RagdollParams ragdoll=null, bool spawnInitialItems=true)
Create a new character
void GiveIdCardTags(WayPoint spawnPoint, bool createNetworkEvent=false)
CharacterInventory Inventory
readonly CharacterPrefab Prefab
static Character? Controlled
void GiveJobItems(WayPoint spawnPoint=null)
void SpawnInventoryItems(Inventory inventory, ContentXElement itemData)
Stores information about the Character that is needed between rounds in the menu etc....
static void SaveOrders(XElement parentElement, params Order[] orders)
static void ApplyHealthData(Character character, XElement healthData, Func< AfflictionPrefab, bool > afflictionPredicate=null)
static int HighestManualOrderPriority
void Rename(string newName)
static List< Order > LoadOrders(XElement ordersElement)
static void ApplyOrderData(Character character, XElement orderData)
ActiveOrder(Order order, float? fadeOutTime)
Responsible for keeping track of the characters in the player crew, saving and loading their orders,...
void Update(float deltaTime)
void RemoveCharacter(Character character, bool removeInfo=false, bool resetCrewListIndex=true)
Remove the character from the crew (and crew menus).
static Character GetCharacterForQuickAssignment(Order order, Character controlledCharacter, IEnumerable< Character > characters, bool includeSelf=false)
void ClearCharacterInfos()
IEnumerable< Character > GetCharacters()
void RemoveCharacterInfo(CharacterInfo characterInfo)
Remove info of a selected character. The character will not be visible in any menus or the round summ...
void ClearCurrentOrders()
IEnumerable< CharacterInfo > GetCharacterInfos()
Note: this only returns AI characters' infos in multiplayer. The infos are used to manage hiring/firi...
bool IsFired(Character character)
void FireCharacter(CharacterInfo characterInfo)
List< ActiveOrder > ActiveOrders
CrewManager(bool isSinglePlayer)
void AddCharacterInfo(CharacterInfo characterInfo)
void RemoveCharacterFromCrewList(Character character)
bool AddOrder(Order order, float? fadeOutTime)
static IEnumerable< Character > GetCharactersSortedForOrder(Order order, IEnumerable< Character > characters, Character controlledCharacter, bool includeSelf, IEnumerable< Character > extraCharacters=null)
void AddCurrentOrderIcon(Character character, Order order)
Displays the specified order in the crew UI next to the character.
List< WayPoint > GetOutpostSpawnpoints()
Returns the potential crew spawnpositions for the crew in the loaded outpost
void AddConversation(List<(Character speaker, string line)> conversationLines)
GUIComponent AddCharacterToCrewList(Character character)
Add character to the list without actually adding it to the crew
void InitializeCharacter(Character character, WayPoint mainSubWaypoint, WayPoint spawnWaypoint)
void LoadActiveOrders(XElement element)
ReadyCheck ActiveReadyCheck
void RenameCharacter(CharacterInfo characterInfo, string newName)
void AddCharacterElements(XElement element)
void AddCharacter(Character character, bool sortCrewList=true)
void SaveActiveOrders(XElement element)
static GameSession?? GameSession
static ContentPackage VanillaContent
static GameModePreset TestMode
static bool IsItemTargetedBySomeone(ItemComponent target, CharacterTeamType team, out Character operatingCharacter)
AIObjectiveManager ObjectiveManager
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
List< ItemComponent > Components
static HashSet< Item > DeconstructItems
Items that have been marked for deconstruction
AIObjectiveIdle.BehaviorType IdleBehavior
bool ShouldSpawnCrewInsideOutpost()
readonly Entity TargetEntity
readonly Character OrderGiver
readonly? int WallSectionIndex
bool HasPreferredJob(Character character)
Identifier AppropriateSkill
readonly OrderPrefab Prefab
bool HasAppropriateJob(Character character)
readonly ItemComponent TargetItemComponent
static readonly PrefabCollection< OrderPrefab > Prefabs
ContentPackage? ContentPackage
readonly bool DisableBotConversations
readonly TutorialPrefab TutorialPrefab
static List< WayPoint > WayPointList
static WayPoint[] SelectCrewSpawnPoints(List< CharacterInfo > crew, Submarine submarine)