Client LuaCsForBarotrauma
AIObjectiveManager.cs
3 using Barotrauma.Networking; // used by the server
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 
9 namespace Barotrauma
10 {
12  {
13  public enum ObjectiveType
14  {
15  None = 0,
16  Order = 1,
17  Objective = 2,
18 
19  MinValue = 0,
20  MaxValue = 2
21  }
22 
27  public const float MaxObjectivePriority = 100;
32  public const float EmergencyObjectivePriority = 90;
36  public const float HighestOrderPriority = 70;
40  public const float LowestOrderPriority = 60;
44  public const float RunPriority = 50;
45  // Constantly increases the priority of the selected objective, unless overridden
46  public const float baseDevotion = 5;
47 
51  public List<AIObjective> Objectives { get; private set; } = new List<AIObjective>();
52 
53  private readonly Character character;
54 
56 
57  private float _waitTimer;
61  public float WaitTimer
62  {
63  get { return _waitTimer; }
64  set
65  {
66  _waitTimer = IsAllowedToWait() ? value : 0;
67  }
68  }
69 
70  public List<Order> CurrentOrders { get; } = new List<Order>();
75  {
76  get
77  {
78  return ForcedOrder ?? currentOrder;
79  }
80  private set
81  {
82  currentOrder = value;
83  }
84  }
85  private AIObjective currentOrder;
86  public AIObjective ForcedOrder { get; private set; }
87 
91  public AIObjective CurrentObjective { get; private set; }
92 
93  public AIObjectiveManager(Character character)
94  {
95  this.character = character;
97  }
98 
99  public void AddObjective(AIObjective objective)
100  {
101  AddObjective<AIObjective>(objective);
102  }
103 
104  public void AddObjective<T>(T objective) where T : AIObjective
105  {
106  var result = GameMain.LuaCs.Hook.Call<bool?>("AI.addObjective", this, objective);
107 
108  if (result != null && result.Value) return;
109 
110  if (objective == null)
111  {
112 #if DEBUG
113  DebugConsole.ThrowError("Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace.CleanupStackTrace());
114 #endif
115  return;
116  }
117  // Can't use the generic type, because it's possible that the user of this method uses the base type AIObjective.
118  // We need to get the highest type.
119  var type = objective.GetType();
120  if (objective.AllowMultipleInstances)
121  {
122  if (Objectives.FirstOrDefault(o => o.GetType() == type) is T existingObjective && existingObjective.IsDuplicate(objective))
123  {
124  Objectives.Remove(existingObjective);
125  }
126  }
127  else
128  {
129  Objectives.RemoveAll(o => o.GetType() == type);
130  }
131  Objectives.Add(objective);
132  }
133 
134  public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
135  public bool FailedAutonomousObjectives { get; private set; }
136 
138 
139  private void ClearIgnored()
140  {
141  if (character.AIController is HumanAIController humanAi)
142  {
143  humanAi.UnreachableHulls.Clear();
144  humanAi.IgnoredItems.Clear();
145  }
146  }
147 
149  {
150  if (character.IsDead)
151  {
152 #if DEBUG
153  DebugConsole.ThrowError("Attempted to create autonomous orders for a dead character");
154 #else
155  return;
156 #endif
157  }
158 
159  foreach (var delayedObjective in DelayedObjectives)
160  {
161  CoroutineManager.StopCoroutines(delayedObjective.Value);
162  }
163 
164  var prevIdleObjective = GetObjective<AIObjectiveIdle>();
165 
166  DelayedObjectives.Clear();
167  Objectives.Clear();
169  AddObjective(new AIObjectiveFindSafety(character, this));
170  var newIdleObjective = new AIObjectiveIdle(character, this);
171  if (prevIdleObjective != null)
172  {
173  newIdleObjective.TargetHull = prevIdleObjective.TargetHull;
174  newIdleObjective.Behavior = prevIdleObjective.Behavior;
175  prevIdleObjective.PreferredOutpostModuleTypes.ForEach(t => newIdleObjective.PreferredOutpostModuleTypes.Add(t));
176  }
177  AddObjective(newIdleObjective);
178 
179  int objectiveCount = Objectives.Count;
180  if (character.Info?.Job != null)
181  {
182  foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives)
183  {
184  var orderPrefab = OrderPrefab.Prefabs[autonomousObjective.Identifier] ?? throw new Exception($"Could not find a matching prefab by the identifier: '{autonomousObjective.Identifier}'");
185  Item item = null;
186  if (orderPrefab.MustSetTarget)
187  {
188  item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID, interactableFor: character)?.GetRandomUnsynced();
189  }
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) &&
193  Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC && !character.IsFriendlyNPCTurnedHostile)
194  {
195  if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
196  {
197  continue;
198  }
199  }
200  if (autonomousObjective.IgnoreAtNonOutpost && !Level.IsLoadedFriendlyOutpost)
201  {
202  continue;
203  }
204  var objective = CreateObjective(order, autonomousObjective.PriorityModifier);
205  if (objective != null && objective.CanBeCompleted)
206  {
207  AddObjective(objective, delay: Rand.Value() / 2);
208  objectiveCount++;
209  }
210  }
211  }
212  else
213  {
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);
218  }
219 
220  _waitTimer = Math.Max(_waitTimer, Rand.Range(0.5f, 1f) * objectiveCount);
221  }
222 
223  public void AddObjective<T>(T objective, float delay, Action callback = null) where T : AIObjective
224  {
225  if (objective == null)
226  {
227 #if DEBUG
228  DebugConsole.ThrowError($"{character.Name}: Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace.CleanupStackTrace());
229 #endif
230  return;
231  }
232  if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine))
233  {
234  CoroutineManager.StopCoroutines(coroutine);
235  DelayedObjectives.Remove(objective);
236  }
237  coroutine = CoroutineManager.Invoke(() =>
238  {
239  //round ended before the coroutine finished
240  if (GameMain.GameSession == null || Level.Loaded == null && GameMain.GameSession.GameMode is not TestGameMode) { return; }
241  DelayedObjectives.Remove(objective);
242  AddObjective(objective);
243  callback?.Invoke();
244  }, delay);
245  DelayedObjectives.Add(objective, coroutine);
246  }
247 
248  public T GetObjective<T>() where T : AIObjective => Objectives.FirstOrDefault(o => o is T) as T;
249 
250  private AIObjective GetCurrentObjective()
251  {
252  var previousObjective = CurrentObjective;
253  var firstObjective = Objectives.FirstOrDefault();
254 
255  bool currentObjectiveIsOrder = CurrentOrder != null && firstObjective != null && CurrentOrder.Priority > firstObjective.Priority;
256 
257  CurrentObjective = currentObjectiveIsOrder ? CurrentOrder : firstObjective;
258 
259  if (previousObjective == CurrentObjective) { return CurrentObjective; }
260 
261  previousObjective?.OnDeselected();
262  if (CurrentObjective != null)
263  {
265  GetObjective<AIObjectiveIdle>().CalculatePriority(Math.Max(CurrentObjective.Priority - 10, 0));
266  }
267  if (GameMain.NetworkMember is { IsServer: true })
268  {
269  GameMain.NetworkMember.CreateEntityEvent(character,
270  new Character.ObjectiveManagerStateEventData(currentObjectiveIsOrder ? ObjectiveType.Order : ObjectiveType.Objective));
271  }
272  return CurrentObjective;
273  }
274 
278  public float GetCurrentPriority()
279  {
280  if (CurrentObjective == null) { return 0; }
281  float subObjectivePriority = CurrentObjective.SubObjectives.Any() ? CurrentObjective.SubObjectives.Max(so => so.Priority) : 0;
282  return Math.Max(CurrentObjective.Priority, subObjectivePriority);
283  }
284 
285  public void UpdateObjectives(float deltaTime)
286  {
287  UpdateOrderObjective(ForcedOrder);
288 
289  if (CurrentOrders.Any())
290  {
291  foreach (var order in CurrentOrders)
292  {
293  var orderObjective = order.Objective;
294  UpdateOrderObjective(orderObjective);
295  }
296  }
297 
298  void UpdateOrderObjective(AIObjective orderObjective)
299  {
300  if (orderObjective == null) { return; }
301 #if DEBUG
302  // Note: don't automatically remove orders here. Removing orders needs to be done via dismissing.
303  if (!orderObjective.CanBeCompleted)
304  {
305  DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
306  }
307 #endif
308  orderObjective.Update(deltaTime);
309  }
310 
311  if (WaitTimer > 0)
312  {
313  WaitTimer -= deltaTime;
314  return;
315  }
316  for (int i = 0; i < Objectives.Count; i++)
317  {
318  var objective = Objectives[i];
319  if (objective.IsCompleted)
320  {
321 #if DEBUG
322  DebugConsole.NewMessage($"{character.Name}: Removing objective {objective.DebugTag}, because it is completed.", Color.LightBlue);
323 #endif
324  Objectives.Remove(objective);
325  }
326  else if (!objective.CanBeCompleted)
327  {
328 #if DEBUG
329  DebugConsole.NewMessage($"{character.Name}: Removing objective {objective.DebugTag}, because it cannot be completed.", Color.Red);
330 #endif
331  Objectives.Remove(objective);
333  }
334  else
335  {
336  objective.Update(deltaTime);
337  }
338  }
339  GetCurrentObjective();
340  }
341 
342  public void SortObjectives()
343  {
345  AIObjective orderWithHighestPriority = null;
346  float highestPriority = 0;
347  for (int i = CurrentOrders.Count - 1; i >= 0; i--)
348  {
349  if (CurrentOrders.Count <= i) { break; }
350  var orderObjective = CurrentOrders[i].Objective;
351  if (orderObjective == null) { continue; }
352  orderObjective.CalculatePriority();
353  if (orderWithHighestPriority == null || orderObjective.Priority > highestPriority)
354  {
355  orderWithHighestPriority = orderObjective;
356  highestPriority = orderObjective.Priority;
357  }
358  }
359  CurrentOrder = orderWithHighestPriority;
360  for (int i = Objectives.Count - 1; i >= 0; i--)
361  {
362  Objectives[i].CalculatePriority();
363  }
364  if (Objectives.Any())
365  {
366  Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
367  }
368  GetCurrentObjective()?.SortSubObjectives();
369  }
370 
371  public void DoCurrentObjective(float deltaTime)
372  {
373  if (WaitTimer <= 0)
374  {
375  CurrentObjective?.TryComplete(deltaTime);
376  }
377  else
378  {
379  character.AIController.SteeringManager.Reset();
380  }
381  }
382 
383  public void SetForcedOrder(AIObjective objective)
384  {
385  ForcedOrder = objective;
386  }
387 
388  public void ClearForcedOrder()
389  {
390  ForcedOrder = null;
391  SortObjectives();
392  }
393 
394  public void SetOrder(Order order, bool speak)
395  {
396  if (character.IsDead)
397  {
398 #if DEBUG
399  DebugConsole.ThrowError("Attempted to set an order for a dead character");
400 #else
401  return;
402 #endif
403  }
404  ClearIgnored();
405 
406  if (order == null || order.IsDismissal)
407  {
408  if (order.Option != Identifier.Empty)
409  {
410  if (CurrentOrders.Any(o => o.MatchesDismissedOrder(order.Option)))
411  {
412  var dismissedOrderInfo = CurrentOrders.First(o => o.MatchesDismissedOrder(order.Option));
413  CurrentOrders.Remove(dismissedOrderInfo);
414  }
415  }
416  else
417  {
418  CurrentOrders.Clear();
419  }
420  }
421 
422  // Make sure the order priorities reflect those set by the player
423  for (int i = CurrentOrders.Count - 1; i >= 0; i--)
424  {
425  if (CurrentOrders.Count <= i) { break; }
426  var currentOrder = CurrentOrders[i];
427  if (currentOrder.Objective == null || currentOrder.MatchesOrder(order))
428  {
429  CurrentOrders.RemoveAt(i);
430  continue;
431  }
432  if (character.GetCurrentOrder(currentOrder) is Order currentOrderInfo)
433  {
434  int currentPriority = currentOrderInfo.ManualPriority;
435  if (currentOrder.ManualPriority != currentPriority)
436  {
437  CurrentOrders[i] = currentOrder.WithManualPriority(currentPriority);
438  }
439  }
440  else
441  {
442  CurrentOrders.RemoveAt(i);
443  }
444  }
445 
446  //reset this here so the bots can retry finding a better suit if it's needed for the new order
448 
449  var newCurrentObjective = CreateObjective(order);
450  if (newCurrentObjective != null)
451  {
452  newCurrentObjective.Abandoned += () => DismissSelf(order);
453  CurrentOrders.Add(order.WithObjective(newCurrentObjective));
454  }
455  if (!HasOrders())
456  {
457  // Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
459  }
460  else if (newCurrentObjective != null)
461  {
462  if (speak && character.IsOnPlayerTeam)
463  {
464  LocalizedString msg = newCurrentObjective.IsAllowed ? TextManager.Get("DialogAffirmative") : TextManager.Get("DialogNegative");
465  character.Speak(msg.Value, delay: 1.0f);
466  }
467  }
468  }
469 
470  public AIObjective CreateObjective(Order order, float priorityModifier = 1)
471  {
472  if (order == null || order.IsDismissal) { return null; }
473  AIObjective newObjective;
474  switch (order.Identifier.Value.ToLowerInvariant())
475  {
476  case "follow":
477  if (order.OrderGiver == null) { return null; }
478  newObjective = new AIObjectiveGoTo(order.OrderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
479  {
480  CloseEnough = Rand.Range(80f, 100f),
481  CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountBotsInTheCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == order.OrderGiver)) * Rand.Range(0.8f, 1f), 4),
482  ExtraDistanceOutsideSub = 100,
483  ExtraDistanceWhileSwimming = 100,
484  AllowGoingOutside = true,
485  IgnoreIfTargetDead = true,
486  IsFollowOrder = true,
487  Mimic = character.IsOnPlayerTeam,
488  DialogueIdentifier = "dialogcannotreachplace".ToIdentifier()
489  };
490  break;
491  case "wait":
492  newObjective = new AIObjectiveGoTo(order.TargetSpatialEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
493  {
494  AllowGoingOutside = true,
495  IsWaitOrder = true,
496  DebugLogWhenFails = false,
497  SpeakIfFails = false,
498  CloseEnough = 100
499  };
500  break;
501  case "return":
502  newObjective = new AIObjectiveReturn(character, order.OrderGiver, this, priorityModifier: priorityModifier);
503  newObjective.Completed += () => DismissSelf(order);
504  break;
505  case "fixleaks":
506  newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier: priorityModifier, prioritizedHull: order.TargetEntity as Hull);
507  break;
508  case "chargebatteries":
509  newObjective = new AIObjectiveChargeBatteries(character, this, order.Option, priorityModifier);
510  break;
511  case "rescue":
512  newObjective = new AIObjectiveRescueAll(character, this, priorityModifier);
513  break;
514  case "repairsystems":
515  case "repairmechanical":
516  case "repairelectrical":
517  newObjective = new AIObjectiveRepairItems(character, this, priorityModifier: priorityModifier, prioritizedItem: order.TargetEntity as Item)
518  {
519  RelevantSkill = order.AppropriateSkill,
520  };
521  break;
522  case "pumpwater":
523  if (order.TargetItemComponent is Pump targetPump)
524  {
525  if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
526  newObjective = new AIObjectiveOperateItem(targetPump, character, this, order.Option, false, priorityModifier: priorityModifier)
527  {
528  Override = order.OrderGiver is { IsCommanding: true }
529  };
530  newObjective.Completed += () => DismissSelf(order);
531  }
532  else
533  {
534  newObjective = new AIObjectivePumpWater(character, this, order.Option, priorityModifier: priorityModifier);
535  }
536  break;
537  case "extinguishfires":
538  newObjective = new AIObjectiveExtinguishFires(character, this, priorityModifier);
539  break;
540  case "fightintruders":
541  newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier);
542  break;
543  case "assaultenemy":
544  newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier)
545  {
546  TargetCharactersInOtherSubs = true
547  };
548  break;
549  case "steer":
550  var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
551  if (steering != null) { steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; }
552  if (order.TargetItemComponent == null) { return null; }
553  if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
554  newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option,
555  requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
556  {
557  Repeat = true,
558  // Don't override unless it's an order by a player
559  Override = order.OrderGiver != null && order.OrderGiver.IsCommanding
560  };
561  break;
562  case "setchargepct":
563  newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option, false, priorityModifier: priorityModifier)
564  {
565  Override = !character.IsDismissed,
566  completionCondition = () =>
567  {
568  if (float.TryParse(order.Option.Value, out float pct))
569  {
570  var targetRatio = Math.Clamp(pct, 0f, 1f);
571  var currentRatio = (order.TargetItemComponent as PowerContainer).RechargeRatio;
572  return Math.Abs(targetRatio - currentRatio) < 0.05f;
573  }
574  return true;
575  }
576  };
577  break;
578  case "getitem":
579  newObjective = new AIObjectiveGetItem(character, order.TargetEntity as Item ?? order.TargetItemComponent?.Item, this, false, priorityModifier: priorityModifier)
580  {
581  MustBeSpecificItem = true
582  };
583  break;
584  case "cleanupitems":
585  if (order.TargetEntity is Item targetItem)
586  {
587  if (targetItem.HasTag(Tags.AllowCleanup) && targetItem.ParentInventory == null && targetItem.OwnInventory != null)
588  {
589  // Target all items inside the container
590  newObjective = new AIObjectiveCleanupItems(character, this, targetItem.OwnInventory.AllItems, priorityModifier);
591  }
592  else
593  {
594  newObjective = new AIObjectiveCleanupItems(character, this, targetItem, priorityModifier);
595  }
596  }
597  else
598  {
599  newObjective = new AIObjectiveCleanupItems(character, this, priorityModifier: priorityModifier);
600  }
601  break;
602  case "escapehandcuffs":
603  newObjective = new AIObjectiveEscapeHandcuffs(character, this, priorityModifier: priorityModifier);
604  break;
605  case "findthieves":
606  newObjective = new AIObjectiveFindThieves(character, this, priorityModifier: priorityModifier);
607  break;
608  case "prepareforexpedition":
609  newObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(order.Option), order.RequireItems)
610  {
611  KeepActiveWhenReady = true,
612  CheckInventory = true,
613  Equip = false,
614  FindAllItems = true,
615  RequireNonEmpty = false
616  };
617  break;
618  case "findweapon":
619  AIObjectivePrepare prepareObjective;
620  if (order.TargetEntity is Item tItem)
621  {
622  prepareObjective = new AIObjectivePrepare(character, this, targetItem: tItem);
623  }
624  else
625  {
626  prepareObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(order.Option), order.RequireItems)
627  {
628  CheckInventory = false,
629  EvaluateCombatPriority = true,
630  FindAllItems = false,
631  RequireNonEmpty = true
632  };
633  }
634  prepareObjective.KeepActiveWhenReady = false;
635  prepareObjective.Equip = true;
636  newObjective = prepareObjective;
637  newObjective.Completed += () => DismissSelf(order);
638  break;
639  case "loaditems":
640  newObjective = new AIObjectiveLoadItems(character, this, order.Option, order.GetTargetItems(order.Option), order.TargetEntity as Item, priorityModifier);
641  break;
642  case "deconstructitems":
643  newObjective = new AIObjectiveDeconstructItems(character, this, priorityModifier);
644  break;
645  case "inspectnoises":
646  newObjective = new AIObjectiveInspectNoises(character, this, priorityModifier);
647  break;
648  default:
649  if (order.TargetItemComponent == null) { return null; }
650  if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
651  newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option,
652  requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
653  {
654  Repeat = true,
655  // Don't override unless it's an order by a player
656  Override = order.OrderGiver != null && order.OrderGiver.IsCommanding
657  };
658  if (newObjective.Abandon) { return null; }
659  break;
660  }
661  if (newObjective != null)
662  {
663  newObjective.Identifier = order.Identifier;
664  }
665  newObjective.IgnoreAtOutpost = order.IgnoreAtOutpost;
666  return newObjective;
667  }
668 
674  private void DismissSelf(Order order)
675  {
676  var currentOrder = CurrentOrders.FirstOrDefault(oi => oi.MatchesOrder(order.Identifier, order.Option));
677  if (currentOrder == null)
678  {
679 #if DEBUG
680  DebugConsole.ThrowError("Tried to self-dismiss an order, but no matching current order was found");
681 #endif
682  return;
683  }
684 
685  Order dismissOrder = currentOrder.GetDismissal();
686 #if CLIENT
687  if (GameMain.GameSession?.CrewManager is CrewManager cm && cm.IsSinglePlayer)
688  {
689  character.SetOrder(dismissOrder, isNewOrder: true, speak: false);
690  }
691 #else
692  GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(dismissOrder, character, character));
693  SetOrder(dismissOrder, speak: false);
694 #endif
695  }
696 
697  private bool IsAllowedToWait()
698  {
699  if (!character.IsOnPlayerTeam) { return false; }
700  if (HasOrders()) { return false; }
701  if (CurrentObjective is AIObjectiveCombat || CurrentObjective is AIObjectiveFindSafety) { return false; }
702  if (character.AnimController.InWater) { return false; }
703  if (character.IsClimbing) { return false; }
704  if (character.AIController is HumanAIController humanAI)
705  {
706  if (humanAI.UnsafeHulls.Contains(character.CurrentHull)) { return false; }
707  }
708  if (AIObjectiveIdle.IsForbidden(character.CurrentHull)) { return false; }
709  return true;
710  }
711 
715  public bool IsCurrentOrder<T>() where T : AIObjective => CurrentOrder is T;
716 
720  public bool IsCurrentObjective<T>() where T : AIObjective => CurrentObjective is T;
721 
725  public bool HasObjectiveOrOrder<T>() where T : AIObjective => Objectives.Any(o => o is T) || HasOrder<T>();
726 
728 
732  public T GetOrder<T>() where T : AIObjective => CurrentOrders.FirstOrDefault(o => o.Objective is T)?.Objective as T;
733 
737  public Order GetOrder(AIObjective objective) => CurrentOrders.FirstOrDefault(o => o.Objective == objective);
738 
745  public T GetLastActiveObjective<T>() where T : AIObjective
746  => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
747 
755  public T GetFirstActiveObjective<T>() where T : AIObjective
756  => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).FirstOrDefault(so => so is T) as T;
757 
761  public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective
762  {
763  if (CurrentObjective == null) { return Enumerable.Empty<T>(); }
764  return CurrentObjective.GetSubObjectivesRecursive(includingSelf: true).OfType<T>();
765  }
766 
767  public bool HasActiveObjective<T>() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T);
768 
769  public bool IsOrder(AIObjective objective)
770  {
771  if (objective == ForcedOrder) { return true; }
772  foreach (var order in CurrentOrders)
773  {
774  if (order.Objective == objective) { return true; }
775  }
776  return false;
777  }
778 
779  public bool HasOrders()
780  {
781  return ForcedOrder != null || CurrentOrders.Any();
782  }
783 
784  public bool HasOrder<T>(Func<T, bool> predicate = null) where T : AIObjective =>
785  ForcedOrder is T forcedOrder && (predicate == null || predicate(forcedOrder)) ||
786  CurrentOrders.Any(o => o.Objective is T order && (predicate == null || predicate(order)));
787 
788  public float GetOrderPriority(AIObjective objective)
789  {
790  if (objective == ForcedOrder)
791  {
792  return HighestOrderPriority;
793  }
794  var currentOrder = CurrentOrders.FirstOrDefault(o => o.Objective == objective);
795  if (currentOrder.Objective == null)
796  {
797  return HighestOrderPriority;
798  }
799  else if (currentOrder.ManualPriority > 0)
800  {
801  if (objective.ForceHighestPriority)
802  {
803  return HighestOrderPriority;
804  }
805  if (objective.PrioritizeIfSubObjectivesActive && objective.SubObjectives.Any())
806  {
807  return HighestOrderPriority;
808  }
809  return MathHelper.Lerp(LowestOrderPriority, HighestOrderPriority - 1, MathUtils.InverseLerp(1, CharacterInfo.HighestManualOrderPriority, currentOrder.ManualPriority));
810  }
811 #if DEBUG
812  DebugConsole.AddWarning("Error in order priority: shouldn't return 0!");
813 #endif
814  return 0;
815  }
816 
818  {
819  if (currentOrder == null) { return null; }
820  return CurrentOrders.FirstOrDefault(o => o.Objective == CurrentOrder);
821  }
822  }
823 }
float CalculatePriority()
Call this only when the priority needs to be recalculated. Use the cached Priority property when you ...
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 (...
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()
const float RunPriority
Objectives with a priority equal to or higher than this make the character run.
Dictionary< AIObjective, CoroutineHandle > DelayedObjectives
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)
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
void DoCurrentObjective(float deltaTime)
Order GetOrder(AIObjective objective)
Return the first order with the specified objective. Can return null.
float GetOrderPriority(AIObjective objective)
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 GameSession?? GameSession
Definition: GameMain.cs:88
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static LuaCsSetup LuaCs
Definition: GameMain.cs:26
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
Definition: Order.cs:491
readonly Character OrderGiver
Definition: Order.cs:495
ISpatialEntity??? TargetSpatialEntity
Note this property doesn't return the follow target of the Follow objective, as expected!
Definition: Order.cs:505
bool IsDismissal
Definition: Order.cs:483
readonly Identifier Option
Definition: Order.cs:478
bool IgnoreAtOutpost
Definition: Order.cs:558
readonly bool UseController
Definition: Order.cs:566
Identifier AppropriateSkill
Definition: Order.cs:551
readonly Controller ConnectedController
Definition: Order.cs:493
ref readonly ImmutableArray< Identifier > RequireItems
Definition: Order.cs:541
Identifier Identifier
Definition: Order.cs:536
ImmutableArray< Identifier > GetTargetItems(Identifier option=default)
Order WithObjective(AIObjective objective)
Definition: Order.cs:715
readonly ItemComponent TargetItemComponent
Definition: Order.cs:492
static readonly PrefabCollection< OrderPrefab > Prefabs
Definition: Order.cs:41
static Submarine MainSub
Note that this can be null in some situations, e.g. editors and missions that don't load a submarine.