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 CLIENT
241  if (GameMain.GameSession == null || Level.Loaded == null && !(GameMain.GameSession.GameMode is TestGameMode)) { return; }
242 #else
243  if (GameMain.GameSession == null || Level.Loaded == null) { return; }
244 #endif
245  DelayedObjectives.Remove(objective);
246  AddObjective(objective);
247  callback?.Invoke();
248  }, delay);
249  DelayedObjectives.Add(objective, coroutine);
250  }
251 
252  public T GetObjective<T>() where T : AIObjective => Objectives.FirstOrDefault(o => o is T) as T;
253 
254  private AIObjective GetCurrentObjective()
255  {
256  var previousObjective = CurrentObjective;
257  var firstObjective = Objectives.FirstOrDefault();
258 
259  bool currentObjectiveIsOrder = CurrentOrder != null && firstObjective != null && CurrentOrder.Priority > firstObjective.Priority;
260 
261  CurrentObjective = currentObjectiveIsOrder ? CurrentOrder : firstObjective;
262 
263  if (previousObjective == CurrentObjective) { return CurrentObjective; }
264 
265  previousObjective?.OnDeselected();
266  if (CurrentObjective != null)
267  {
269  GetObjective<AIObjectiveIdle>().CalculatePriority(Math.Max(CurrentObjective.Priority - 10, 0));
270  }
271  if (GameMain.NetworkMember is { IsServer: true })
272  {
273  GameMain.NetworkMember.CreateEntityEvent(character,
274  new Character.ObjectiveManagerStateEventData(currentObjectiveIsOrder ? ObjectiveType.Order : ObjectiveType.Objective));
275  }
276  return CurrentObjective;
277  }
278 
282  public float GetCurrentPriority()
283  {
284  if (CurrentObjective == null) { return 0; }
285  float subObjectivePriority = CurrentObjective.SubObjectives.Any() ? CurrentObjective.SubObjectives.Max(so => so.Priority) : 0;
286  return Math.Max(CurrentObjective.Priority, subObjectivePriority);
287  }
288 
289  public void UpdateObjectives(float deltaTime)
290  {
291  UpdateOrderObjective(ForcedOrder);
292 
293  if (CurrentOrders.Any())
294  {
295  foreach (var order in CurrentOrders)
296  {
297  var orderObjective = order.Objective;
298  UpdateOrderObjective(orderObjective);
299  }
300  }
301 
302  void UpdateOrderObjective(AIObjective orderObjective)
303  {
304  if (orderObjective == null) { return; }
305 #if DEBUG
306  // Note: don't automatically remove orders here. Removing orders needs to be done via dismissing.
307  if (!orderObjective.CanBeCompleted)
308  {
309  DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
310  }
311 #endif
312  orderObjective.Update(deltaTime);
313  }
314 
315  if (WaitTimer > 0)
316  {
317  WaitTimer -= deltaTime;
318  return;
319  }
320  for (int i = 0; i < Objectives.Count; i++)
321  {
322  var objective = Objectives[i];
323  if (objective.IsCompleted)
324  {
325 #if DEBUG
326  DebugConsole.NewMessage($"{character.Name}: Removing objective {objective.DebugTag}, because it is completed.", Color.LightBlue);
327 #endif
328  Objectives.Remove(objective);
329  }
330  else if (!objective.CanBeCompleted)
331  {
332 #if DEBUG
333  DebugConsole.NewMessage($"{character.Name}: Removing objective {objective.DebugTag}, because it cannot be completed.", Color.Red);
334 #endif
335  Objectives.Remove(objective);
337  }
338  else
339  {
340  objective.Update(deltaTime);
341  }
342  }
343  GetCurrentObjective();
344  }
345 
346  public void SortObjectives()
347  {
349  AIObjective orderWithHighestPriority = null;
350  float highestPriority = 0;
351  for (int i = CurrentOrders.Count - 1; i >= 0; i--)
352  {
353  if (CurrentOrders.Count <= i) { break; }
354  var orderObjective = CurrentOrders[i].Objective;
355  if (orderObjective == null) { continue; }
356  orderObjective.CalculatePriority();
357  if (orderWithHighestPriority == null || orderObjective.Priority > highestPriority)
358  {
359  orderWithHighestPriority = orderObjective;
360  highestPriority = orderObjective.Priority;
361  }
362  }
363  CurrentOrder = orderWithHighestPriority;
364  for (int i = Objectives.Count - 1; i >= 0; i--)
365  {
366  Objectives[i].CalculatePriority();
367  }
368  if (Objectives.Any())
369  {
370  Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
371  }
372  GetCurrentObjective()?.SortSubObjectives();
373  }
374 
375  public void DoCurrentObjective(float deltaTime)
376  {
377  if (WaitTimer <= 0)
378  {
379  CurrentObjective?.TryComplete(deltaTime);
380  }
381  else
382  {
383  character.AIController.SteeringManager.Reset();
384  }
385  }
386 
387  public void SetForcedOrder(AIObjective objective)
388  {
389  ForcedOrder = objective;
390  }
391 
392  public void ClearForcedOrder()
393  {
394  ForcedOrder = null;
395  SortObjectives();
396  }
397 
398  public void SetOrder(Order order, bool speak)
399  {
400  if (character.IsDead)
401  {
402 #if DEBUG
403  DebugConsole.ThrowError("Attempted to set an order for a dead character");
404 #else
405  return;
406 #endif
407  }
408  ClearIgnored();
409 
410  if (order == null || order.IsDismissal)
411  {
412  if (order.Option != Identifier.Empty)
413  {
414  if (CurrentOrders.Any(o => o.MatchesDismissedOrder(order.Option)))
415  {
416  var dismissedOrderInfo = CurrentOrders.First(o => o.MatchesDismissedOrder(order.Option));
417  CurrentOrders.Remove(dismissedOrderInfo);
418  }
419  }
420  else
421  {
422  CurrentOrders.Clear();
423  }
424  }
425 
426  // Make sure the order priorities reflect those set by the player
427  for (int i = CurrentOrders.Count - 1; i >= 0; i--)
428  {
429  if (CurrentOrders.Count <= i) { break; }
430  var currentOrder = CurrentOrders[i];
431  if (currentOrder.Objective == null || currentOrder.MatchesOrder(order))
432  {
433  CurrentOrders.RemoveAt(i);
434  continue;
435  }
436  if (character.GetCurrentOrder(currentOrder) is Order currentOrderInfo)
437  {
438  int currentPriority = currentOrderInfo.ManualPriority;
439  if (currentOrder.ManualPriority != currentPriority)
440  {
441  CurrentOrders[i] = currentOrder.WithManualPriority(currentPriority);
442  }
443  }
444  else
445  {
446  CurrentOrders.RemoveAt(i);
447  }
448  }
449 
450  //reset this here so the bots can retry finding a better suit if it's needed for the new order
452 
453  var newCurrentObjective = CreateObjective(order);
454  if (newCurrentObjective != null)
455  {
456  newCurrentObjective.Abandoned += () => DismissSelf(order);
457  CurrentOrders.Add(order.WithObjective(newCurrentObjective));
458  }
459  if (!HasOrders())
460  {
461  // Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
463  }
464  else if (newCurrentObjective != null)
465  {
466  if (speak && character.IsOnPlayerTeam)
467  {
468  LocalizedString msg = newCurrentObjective.IsAllowed ? TextManager.Get("DialogAffirmative") : TextManager.Get("DialogNegative");
469  character.Speak(msg.Value, delay: 1.0f);
470  }
471  }
472  }
473 
474  public AIObjective CreateObjective(Order order, float priorityModifier = 1)
475  {
476  if (order == null || order.IsDismissal) { return null; }
477  AIObjective newObjective;
478  switch (order.Identifier.Value.ToLowerInvariant())
479  {
480  case "follow":
481  if (order.OrderGiver == null) { return null; }
482  newObjective = new AIObjectiveGoTo(order.OrderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
483  {
484  CloseEnough = Rand.Range(80f, 100f),
485  CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountBotsInTheCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == order.OrderGiver)) * Rand.Range(0.8f, 1f), 4),
486  ExtraDistanceOutsideSub = 100,
487  ExtraDistanceWhileSwimming = 100,
488  AllowGoingOutside = true,
489  IgnoreIfTargetDead = true,
490  IsFollowOrder = true,
491  Mimic = character.IsOnPlayerTeam,
492  DialogueIdentifier = "dialogcannotreachplace".ToIdentifier()
493  };
494  break;
495  case "wait":
496  newObjective = new AIObjectiveGoTo(order.TargetSpatialEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
497  {
498  AllowGoingOutside = true,
499  IsWaitOrder = true,
500  DebugLogWhenFails = false,
501  SpeakIfFails = false,
502  CloseEnough = 100
503  };
504  break;
505  case "return":
506  newObjective = new AIObjectiveReturn(character, order.OrderGiver, this, priorityModifier: priorityModifier);
507  newObjective.Completed += () => DismissSelf(order);
508  break;
509  case "fixleaks":
510  newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier: priorityModifier, prioritizedHull: order.TargetEntity as Hull);
511  break;
512  case "chargebatteries":
513  newObjective = new AIObjectiveChargeBatteries(character, this, order.Option, priorityModifier);
514  break;
515  case "rescue":
516  newObjective = new AIObjectiveRescueAll(character, this, priorityModifier);
517  break;
518  case "repairsystems":
519  case "repairmechanical":
520  case "repairelectrical":
521  newObjective = new AIObjectiveRepairItems(character, this, priorityModifier: priorityModifier, prioritizedItem: order.TargetEntity as Item)
522  {
523  RelevantSkill = order.AppropriateSkill,
524  };
525  break;
526  case "pumpwater":
527  if (order.TargetItemComponent is Pump targetPump)
528  {
529  if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
530  newObjective = new AIObjectiveOperateItem(targetPump, character, this, order.Option, false, priorityModifier: priorityModifier)
531  {
532  Override = order.OrderGiver is { IsCommanding: true }
533  };
534  newObjective.Completed += () => DismissSelf(order);
535  }
536  else
537  {
538  newObjective = new AIObjectivePumpWater(character, this, order.Option, priorityModifier: priorityModifier);
539  }
540  break;
541  case "extinguishfires":
542  newObjective = new AIObjectiveExtinguishFires(character, this, priorityModifier);
543  break;
544  case "fightintruders":
545  newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier);
546  break;
547  case "assaultenemy":
548  newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier)
549  {
550  TargetCharactersInOtherSubs = true
551  };
552  break;
553  case "steer":
554  var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
555  if (steering != null) { steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; }
556  if (order.TargetItemComponent == null) { return null; }
557  if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
558  newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option,
559  requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
560  {
561  Repeat = true,
562  // Don't override unless it's an order by a player
563  Override = order.OrderGiver != null && order.OrderGiver.IsCommanding
564  };
565  break;
566  case "setchargepct":
567  newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option, false, priorityModifier: priorityModifier)
568  {
569  Override = !character.IsDismissed,
570  completionCondition = () =>
571  {
572  if (float.TryParse(order.Option.Value, out float pct))
573  {
574  var targetRatio = Math.Clamp(pct, 0f, 1f);
575  var currentRatio = (order.TargetItemComponent as PowerContainer).RechargeRatio;
576  return Math.Abs(targetRatio - currentRatio) < 0.05f;
577  }
578  return true;
579  }
580  };
581  break;
582  case "getitem":
583  newObjective = new AIObjectiveGetItem(character, order.TargetEntity as Item ?? order.TargetItemComponent?.Item, this, false, priorityModifier: priorityModifier)
584  {
585  MustBeSpecificItem = true
586  };
587  break;
588  case "cleanupitems":
589  if (order.TargetEntity is Item targetItem)
590  {
591  if (targetItem.HasTag(Tags.AllowCleanup) && targetItem.ParentInventory == null && targetItem.OwnInventory != null)
592  {
593  // Target all items inside the container
594  newObjective = new AIObjectiveCleanupItems(character, this, targetItem.OwnInventory.AllItems, priorityModifier);
595  }
596  else
597  {
598  newObjective = new AIObjectiveCleanupItems(character, this, targetItem, priorityModifier);
599  }
600  }
601  else
602  {
603  newObjective = new AIObjectiveCleanupItems(character, this, priorityModifier: priorityModifier);
604  }
605  break;
606  case "escapehandcuffs":
607  newObjective = new AIObjectiveEscapeHandcuffs(character, this, priorityModifier: priorityModifier);
608  break;
609  case "findthieves":
610  newObjective = new AIObjectiveFindThieves(character, this, priorityModifier: priorityModifier);
611  break;
612  case "prepareforexpedition":
613  newObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(order.Option), order.RequireItems)
614  {
615  KeepActiveWhenReady = true,
616  CheckInventory = true,
617  Equip = false,
618  FindAllItems = true,
619  RequireNonEmpty = false
620  };
621  break;
622  case "findweapon":
623  AIObjectivePrepare prepareObjective;
624  if (order.TargetEntity is Item tItem)
625  {
626  prepareObjective = new AIObjectivePrepare(character, this, targetItem: tItem);
627  }
628  else
629  {
630  prepareObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(order.Option), order.RequireItems)
631  {
632  CheckInventory = false,
633  EvaluateCombatPriority = true,
634  FindAllItems = false,
635  RequireNonEmpty = true
636  };
637  }
638  prepareObjective.KeepActiveWhenReady = false;
639  prepareObjective.Equip = true;
640  newObjective = prepareObjective;
641  newObjective.Completed += () => DismissSelf(order);
642  break;
643  case "loaditems":
644  newObjective = new AIObjectiveLoadItems(character, this, order.Option, order.GetTargetItems(order.Option), order.TargetEntity as Item, priorityModifier);
645  break;
646  case "deconstructitems":
647  newObjective = new AIObjectiveDeconstructItems(character, this, priorityModifier);
648  break;
649  case "inspectnoises":
650  newObjective = new AIObjectiveInspectNoises(character, this, priorityModifier);
651  break;
652  default:
653  if (order.TargetItemComponent == null) { return null; }
654  if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
655  newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option,
656  requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
657  {
658  Repeat = true,
659  // Don't override unless it's an order by a player
660  Override = order.OrderGiver != null && order.OrderGiver.IsCommanding
661  };
662  if (newObjective.Abandon) { return null; }
663  break;
664  }
665  if (newObjective != null)
666  {
667  newObjective.Identifier = order.Identifier;
668  }
669  newObjective.IgnoreAtOutpost = order.IgnoreAtOutpost;
670  return newObjective;
671  }
672 
678  private void DismissSelf(Order order)
679  {
680  var currentOrder = CurrentOrders.FirstOrDefault(oi => oi.MatchesOrder(order.Identifier, order.Option));
681  if (currentOrder == null)
682  {
683 #if DEBUG
684  DebugConsole.ThrowError("Tried to self-dismiss an order, but no matching current order was found");
685 #endif
686  return;
687  }
688 
689  Order dismissOrder = currentOrder.GetDismissal();
690 #if CLIENT
691  if (GameMain.GameSession?.CrewManager is CrewManager cm && cm.IsSinglePlayer)
692  {
693  character.SetOrder(dismissOrder, isNewOrder: true, speak: false);
694  }
695 #else
696  GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(dismissOrder, character, character));
697  SetOrder(dismissOrder, speak: false);
698 #endif
699  }
700 
701  private bool IsAllowedToWait()
702  {
703  if (!character.IsOnPlayerTeam) { return false; }
704  if (HasOrders()) { return false; }
705  if (CurrentObjective is AIObjectiveCombat || CurrentObjective is AIObjectiveFindSafety) { return false; }
706  if (character.AnimController.InWater) { return false; }
707  if (character.IsClimbing) { return false; }
708  if (character.AIController is HumanAIController humanAI)
709  {
710  if (humanAI.UnsafeHulls.Contains(character.CurrentHull)) { return false; }
711  }
712  if (AIObjectiveIdle.IsForbidden(character.CurrentHull)) { return false; }
713  return true;
714  }
715 
719  public bool IsCurrentOrder<T>() where T : AIObjective => CurrentOrder is T;
720 
724  public bool IsCurrentObjective<T>() where T : AIObjective => CurrentObjective is T;
725 
729  public bool HasObjectiveOrOrder<T>() where T : AIObjective => Objectives.Any(o => o is T) || HasOrder<T>();
730 
732 
736  public T GetOrder<T>() where T : AIObjective => CurrentOrders.FirstOrDefault(o => o.Objective is T)?.Objective as T;
737 
741  public Order GetOrder(AIObjective objective) => CurrentOrders.FirstOrDefault(o => o.Objective == objective);
742 
749  public T GetLastActiveObjective<T>() where T : AIObjective
750  => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
751 
759  public T GetFirstActiveObjective<T>() where T : AIObjective
760  => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).FirstOrDefault(so => so is T) as T;
761 
765  public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective
766  {
767  if (CurrentObjective == null) { return Enumerable.Empty<T>(); }
768  return CurrentObjective.GetSubObjectivesRecursive(includingSelf: true).OfType<T>();
769  }
770 
771  public bool HasActiveObjective<T>() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T);
772 
773  public bool IsOrder(AIObjective objective)
774  {
775  if (objective == ForcedOrder) { return true; }
776  foreach (var order in CurrentOrders)
777  {
778  if (order.Objective == objective) { return true; }
779  }
780  return false;
781  }
782 
783  public bool HasOrders()
784  {
785  return ForcedOrder != null || CurrentOrders.Any();
786  }
787 
788  public bool HasOrder<T>(Func<T, bool> predicate = null) where T : AIObjective =>
789  ForcedOrder is T forcedOrder && (predicate == null || predicate(forcedOrder)) ||
790  CurrentOrders.Any(o => o.Objective is T order && (predicate == null || predicate(order)));
791 
792  public float GetOrderPriority(AIObjective objective)
793  {
794  if (objective == ForcedOrder)
795  {
796  return HighestOrderPriority;
797  }
798  var currentOrder = CurrentOrders.FirstOrDefault(o => o.Objective == objective);
799  if (currentOrder.Objective == null)
800  {
801  return HighestOrderPriority;
802  }
803  else if (currentOrder.ManualPriority > 0)
804  {
805  if (objective.ForceHighestPriority)
806  {
807  return HighestOrderPriority;
808  }
809  if (objective.PrioritizeIfSubObjectivesActive && objective.SubObjectives.Any())
810  {
811  return HighestOrderPriority;
812  }
813  return MathHelper.Lerp(LowestOrderPriority, HighestOrderPriority - 1, MathUtils.InverseLerp(1, CharacterInfo.HighestManualOrderPriority, currentOrder.ManualPriority));
814  }
815 #if DEBUG
816  DebugConsole.AddWarning("Error in order priority: shouldn't return 0!");
817 #endif
818  return 0;
819  }
820 
822  {
823  if (currentOrder == null) { return null; }
824  return CurrentOrders.FirstOrDefault(o => o.Objective == CurrentOrder);
825  }
826  }
827 }
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:495
readonly Character OrderGiver
Definition: Order.cs:499
ISpatialEntity??? TargetSpatialEntity
Note this property doesn't return the follow target of the Follow objective, as expected!
Definition: Order.cs:509
bool IsDismissal
Definition: Order.cs:487
readonly Identifier Option
Definition: Order.cs:482
bool IgnoreAtOutpost
Definition: Order.cs:562
readonly bool UseController
Definition: Order.cs:570
Identifier AppropriateSkill
Definition: Order.cs:555
readonly Controller ConnectedController
Definition: Order.cs:497
ref readonly ImmutableArray< Identifier > RequireItems
Definition: Order.cs:545
Identifier Identifier
Definition: Order.cs:540
ImmutableArray< Identifier > GetTargetItems(Identifier option=default)
Order WithObjective(AIObjective objective)
Definition: Order.cs:719
readonly ItemComponent TargetItemComponent
Definition: Order.cs:496
static readonly PrefabCollection< OrderPrefab > Prefabs
Definition: Order.cs:41