4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
51 public List<AIObjective>
Objectives {
get;
private set; } =
new List<AIObjective>();
57 private float _waitTimer;
63 get {
return _waitTimer; }
66 _waitTimer = IsAllowedToWait() ? value : 0;
95 this.character = character;
101 AddObjective<AIObjective>(objective);
108 if (result !=
null && result.Value)
return;
110 if (objective ==
null)
113 DebugConsole.ThrowError(
"Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace.CleanupStackTrace());
119 var type = objective.GetType();
120 if (objective.AllowMultipleInstances)
122 if (
Objectives.FirstOrDefault(o => o.GetType() == type) is T existingObjective && existingObjective.IsDuplicate(objective))
129 Objectives.RemoveAll(o => o.GetType() == type);
134 public Dictionary<AIObjective, CoroutineHandle>
DelayedObjectives {
get;
private set; } =
new Dictionary<AIObjective, CoroutineHandle>();
139 private void ClearIgnored()
143 humanAi.UnreachableHulls.Clear();
144 humanAi.IgnoredItems.Clear();
150 if (character.IsDead)
153 DebugConsole.ThrowError(
"Attempted to create autonomous orders for a dead character");
161 CoroutineManager.StopCoroutines(delayedObjective.Value);
164 var prevIdleObjective = GetObjective<AIObjectiveIdle>();
171 if (prevIdleObjective !=
null)
173 newIdleObjective.TargetHull = prevIdleObjective.TargetHull;
174 newIdleObjective.Behavior = prevIdleObjective.Behavior;
175 prevIdleObjective.PreferredOutpostModuleTypes.ForEach(t => newIdleObjective.PreferredOutpostModuleTypes.Add(t));
180 if (character.Info?.Job !=
null)
182 foreach (var autonomousObjective
in character.Info.Job.Prefab.AutonomousObjectives)
184 var orderPrefab =
OrderPrefab.
Prefabs[autonomousObjective.Identifier] ??
throw new Exception($
"Could not find a matching prefab by the identifier: '{autonomousObjective.Identifier}'");
186 if (orderPrefab.MustSetTarget)
188 item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub:
false, requiredTeam: character.Info.TeamID, interactableFor: character)?.GetRandomUnsynced();
190 var order =
new Order(orderPrefab, autonomousObjective.Option, item ?? character.
CurrentHull as
Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
191 if (order ==
null) {
continue; }
192 if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) &&
204 var objective =
CreateObjective(order, autonomousObjective.PriorityModifier);
205 if (objective !=
null && objective.CanBeCompleted)
214 string warningMsg = character.Info ==
null ?
215 $
"The character {character.DisplayName} has been set to use human ai, but has no {nameof(CharacterInfo)}. This may cause issues with the AI. Consider adding {nameof(CharacterPrefab.HasCharacterInfo)}=\"True\" to the character config." :
216 $
"The character {character.DisplayName} has been set to use human ai, but has no job. This may cause issues with the AI. Consider configuring some jobs for the character type.";
217 DebugConsole.AddWarning(warningMsg, character.Prefab.ContentPackage);
220 _waitTimer = Math.Max(_waitTimer, Rand.Range(0.5f, 1f) * objectiveCount);
225 if (objective ==
null)
228 DebugConsole.ThrowError($
"{character.Name}: Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace.CleanupStackTrace());
234 CoroutineManager.StopCoroutines(coroutine);
237 coroutine = CoroutineManager.Invoke(() =>
257 var firstObjective =
Objectives.FirstOrDefault();
297 var orderObjective = order.Objective;
298 UpdateOrderObjective(orderObjective);
302 void UpdateOrderObjective(
AIObjective orderObjective)
304 if (orderObjective ==
null) {
return; }
309 DebugConsole.NewMessage($
"{character.Name}: ORDER {orderObjective.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
312 orderObjective.
Update(deltaTime);
323 if (objective.IsCompleted)
326 DebugConsole.NewMessage($
"{character.Name}: Removing objective {objective.DebugTag}, because it is completed.", Color.LightBlue);
330 else if (!objective.CanBeCompleted)
333 DebugConsole.NewMessage($
"{character.Name}: Removing objective {objective.DebugTag}, because it cannot be completed.", Color.Red);
340 objective.Update(deltaTime);
343 GetCurrentObjective();
350 float highestPriority = 0;
355 if (orderObjective ==
null) {
continue; }
356 orderObjective.CalculatePriority();
357 if (orderWithHighestPriority ==
null || orderObjective.
Priority > highestPriority)
359 orderWithHighestPriority = orderObjective;
360 highestPriority = orderObjective.
Priority;
364 for (
int i =
Objectives.Count - 1; i >= 0; i--)
370 Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
383 character.AIController.SteeringManager.Reset();
400 if (character.IsDead)
403 DebugConsole.ThrowError(
"Attempted to set an order for a dead character");
412 if (order.
Option != Identifier.Empty)
431 if (currentOrder.Objective ==
null || currentOrder.MatchesOrder(order))
436 if (character.GetCurrentOrder(currentOrder) is
Order currentOrderInfo)
438 int currentPriority = currentOrderInfo.ManualPriority;
439 if (currentOrder.ManualPriority != currentPriority)
441 CurrentOrders[i] = currentOrder.WithManualPriority(currentPriority);
454 if (newCurrentObjective !=
null)
456 newCurrentObjective.Abandoned += () => DismissSelf(order);
464 else if (newCurrentObjective !=
null)
466 if (speak && character.IsOnPlayerTeam)
468 LocalizedString msg = newCurrentObjective.IsAllowed ? TextManager.Get(
"DialogAffirmative") : TextManager.Get(
"DialogNegative");
469 character.Speak(msg.
Value, delay: 1.0f);
476 if (order ==
null || order.
IsDismissal) {
return null; }
478 switch (order.
Identifier.Value.ToLowerInvariant())
481 if (order.
OrderGiver ==
null) {
return null; }
484 CloseEnough = Rand.Range(80f, 100f),
486 ExtraDistanceOutsideSub = 100,
487 ExtraDistanceWhileSwimming = 100,
488 AllowGoingOutside =
true,
489 IgnoreIfTargetDead =
true,
490 IsFollowOrder =
true,
491 Mimic = character.IsOnPlayerTeam,
492 DialogueIdentifier =
"dialogcannotreachplace".ToIdentifier()
498 AllowGoingOutside =
true,
500 DebugLogWhenFails =
false,
501 SpeakIfFails =
false,
507 newObjective.
Completed += () => DismissSelf(order);
512 case "chargebatteries":
518 case "repairsystems":
519 case "repairmechanical":
520 case "repairelectrical":
532 Override = order.
OrderGiver is { IsCommanding:
true }
534 newObjective.
Completed += () => DismissSelf(order);
541 case "extinguishfires":
544 case "fightintruders":
550 TargetCharactersInOtherSubs =
true
555 if (steering !=
null) { steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; }
569 Override = !character.IsDismissed,
570 completionCondition = () =>
572 if (
float.TryParse(order.
Option.Value, out
float pct))
574 var targetRatio = Math.Clamp(pct, 0f, 1f);
576 return Math.Abs(targetRatio - currentRatio) < 0.05f;
585 MustBeSpecificItem =
true
591 if (targetItem.HasTag(Tags.AllowCleanup) && targetItem.ParentInventory ==
null && targetItem.OwnInventory !=
null)
594 newObjective =
new AIObjectiveCleanupItems(character,
this, targetItem.OwnInventory.AllItems, priorityModifier);
606 case "escapehandcuffs":
612 case "prepareforexpedition":
615 KeepActiveWhenReady =
true,
616 CheckInventory =
true,
619 RequireNonEmpty =
false
632 CheckInventory =
false,
633 EvaluateCombatPriority =
true,
634 FindAllItems =
false,
635 RequireNonEmpty =
true
639 prepareObjective.
Equip =
true;
640 newObjective = prepareObjective;
641 newObjective.
Completed += () => DismissSelf(order);
646 case "deconstructitems":
649 case "inspectnoises":
662 if (newObjective.
Abandon) {
return null; }
665 if (newObjective !=
null)
678 private void DismissSelf(
Order order)
681 if (currentOrder ==
null)
684 DebugConsole.ThrowError(
"Tried to self-dismiss an order, but no matching current order was found");
689 Order dismissOrder = currentOrder.GetDismissal();
691 if (GameMain.GameSession?.CrewManager is CrewManager cm && cm.IsSinglePlayer)
693 character.SetOrder(dismissOrder, isNewOrder:
true, speak:
false);
696 GameMain.Server?.SendOrderChatMessage(
new OrderChatMessage(dismissOrder, character, character));
697 SetOrder(dismissOrder, speak:
false);
701 private bool IsAllowedToWait()
703 if (!character.IsOnPlayerTeam) {
return false; }
706 if (character.AnimController.InWater) {
return false; }
707 if (character.IsClimbing) {
return false; }
710 if (humanAI.UnsafeHulls.Contains(character.CurrentHull)) {
return false; }
712 if (AIObjectiveIdle.IsForbidden(character.CurrentHull)) {
return false; }
750 =>
CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
760 =>
CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).FirstOrDefault(so => so is T) as T;
765 public IEnumerable<T> GetActiveObjectives<T>() where T :
AIObjective
778 if (order.Objective == objective) {
return true; }
789 ForcedOrder is T forcedOrder && (predicate == null || predicate(forcedOrder)) ||
790 CurrentOrders.Any(o => o.Objective is T order && (predicate == null || predicate(order)));
798 var currentOrder =
CurrentOrders.FirstOrDefault(o => o.Objective == objective);
799 if (currentOrder.Objective ==
null)
803 else if (currentOrder.ManualPriority > 0)
805 if (objective.ForceHighestPriority)
809 if (objective.PrioritizeIfSubObjectivesActive && objective.SubObjectives.Any())
816 DebugConsole.AddWarning(
"Error in order priority: shouldn't return 0!");
823 if (currentOrder ==
null) {
return null; }
AIController(Character c)
IEnumerable< AIObjective > SubObjectives
float CalculatePriority()
Call this only when the priority needs to be recalculated. Use the cached Priority property when you ...
virtual bool CanBeCompleted
virtual void Update(float deltaTime)
IEnumerable< AIObjective > GetSubObjectivesRecursive(bool includingSelf=false)
float Priority
Final priority value after all calculations.
void TryComplete(float deltaTime)
Makes the character act according to the objective, or according to any subobjectives that need to be...
Action Completed
A single shot event. Automatically cleared after launching. Use OnCompleted method for implementing (...
virtual void OnSelected()
virtual void OnDeselected()
abstract Identifier Identifier
const float EmergencyObjectivePriority
Priority of objectives such as finding safety, rescuing someone in a critical state or defending agai...
void SetOrder(Order order, bool speak)
bool IsCurrentOrder< T >()
Only checks the current order. Deprecated, use pattern matching instead.
AIObjective GetActiveObjective()
bool FailedAutonomousObjectives
const float RunPriority
Objectives with a priority equal to or higher than this make the character run.
Dictionary< AIObjective, CoroutineHandle > DelayedObjectives
bool HasActiveObjective< T >()
AIObjective CreateObjective(Order order, float priorityModifier=1)
float GetCurrentPriority()
Returns the highest priority of the current objective and its subobjectives.
AIObjectiveManager(Character character)
List< AIObjective > Objectives
Excluding the current order.
void SetForcedOrder(AIObjective objective)
void AddObjective(AIObjective objective)
HumanAIController HumanAIController
void AddObjective< T >(T objective)
Order GetCurrentOrderInfo()
bool FailedToFindDivingGearForDepth
const float LowestOrderPriority
Maximum priority of an order given to the character (rightmost order in the crew list)
void UpdateObjectives(float deltaTime)
AIObjective CurrentObjective
Includes orders.
AIObjective?? CurrentOrder
The AIObjective in CurrentOrders with the highest AIObjective.Priority
List< Order > CurrentOrders
void DoCurrentObjective(float deltaTime)
Order GetOrder(AIObjective objective)
Return the first order with the specified objective. Can return null.
float GetOrderPriority(AIObjective objective)
void CreateAutonomousObjectives()
bool IsOrder(AIObjective objective)
bool HasOrder< T >(Func< T, bool > predicate=null)
const float MaxObjectivePriority
Highest possible priority for any objective. Used in certain cases where the character needs to react...
float? WaitTimer
When set above zero, the character will stand still doing nothing until the timer runs out....
const float HighestOrderPriority
Maximum priority of an order given to the character (forced order, or the leftmost order in the crew ...
bool IsCommanding
Is the character player or does it have an active ship command manager (an AI controlled sub)?...
Stores information about the Character that is needed between rounds in the menu etc....
static int HighestManualOrderPriority
static GameSession?? GameSession
static NetworkMember NetworkMember
static int CountBotsInTheCrew(Character character, Func< HumanAIController, bool > predicate=null)
bool IsInteractable(Character character)
Returns interactibility based on whether the character is on a player team
static bool IsLoadedFriendlyOutpost
Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1). Does not take reput...
object Call(string name, params object[] args)
readonly Entity TargetEntity
readonly Character OrderGiver
ISpatialEntity??? TargetSpatialEntity
Note this property doesn't return the follow target of the Follow objective, as expected!
readonly Identifier Option
readonly bool UseController
Identifier AppropriateSkill
readonly Controller ConnectedController
ref readonly ImmutableArray< Identifier > RequireItems
ImmutableArray< Identifier > GetTargetItems(Identifier option=default)
Order WithObjective(AIObjective objective)
readonly ItemComponent TargetItemComponent
static readonly PrefabCollection< OrderPrefab > Prefabs
IEnumerable< Submarine > DockedTo