Server LuaCsForBarotrauma
AIObjective.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using Microsoft.Xna.Framework;
6 using System.Collections.Immutable;
7 
8 namespace Barotrauma
9 {
10  abstract partial class AIObjective
11  {
12  public virtual float Devotion => AIObjectiveManager.baseDevotion;
13 
14  public abstract Identifier Identifier { get; set; }
15  public virtual string DebugTag => Identifier.Value;
16  public virtual bool ForceRun => false;
17  public virtual bool IgnoreUnsafeHulls => false;
18  public virtual bool AbandonWhenCannotCompleteSubObjectives => true;
22  public virtual bool AllowSubObjectiveSorting => false;
23  public virtual bool PrioritizeIfSubObjectivesActive => false;
24 
28  public virtual bool AllowMultipleInstances => false;
29 
34  protected virtual bool ConcurrentObjectives => false;
35  public virtual bool KeepDivingGearOn => false;
36  public virtual bool KeepDivingGearOnAlsoWhenInactive => false;
37 
41  public virtual bool AllowAutomaticItemUnequipping => false;
42 
43  // These booleans are used for defining whether the objective is allowed in different contexts. E.g. AllowOutsideSubmarine needs to be true or the objective cannot be active when the bot is swimming outside.
44  protected virtual bool AllowOutsideSubmarine => false;
49  protected virtual bool AllowInFriendlySubs => false;
50  protected virtual bool AllowInAnySub => false;
51  protected virtual bool AllowWhileHandcuffed => true;
52 
58  protected virtual bool AbandonIfDisallowed => true;
59 
60  public virtual bool CanBeCompleted => !Abandon;
61 
62  protected virtual float MaxDevotion => 10;
63 
72 
73  protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
74  private float _cumulatedDevotion;
75  protected float CumulatedDevotion
76  {
77  get { return _cumulatedDevotion; }
78  set { _cumulatedDevotion = MathHelper.Clamp(value, 0, MaxDevotion); }
79  }
80 
84  public float Priority { get; set; }
85  public float BasePriority { get; set; }
86  public float PriorityModifier { get; private set; } = 1;
87 
88  private float resetPriorityTimer;
89  private readonly float resetPriorityTime = 1;
90  private bool _forceHighestPriority;
91  // For forcing the highest priority temporarily. Will reset automatically after one second, unless kept alive by something.
93  {
94  get { return _forceHighestPriority; }
95  set
96  {
97  if (_forceHighestPriority == value) { return; }
98  _forceHighestPriority = value;
99  if (_forceHighestPriority)
100  {
101  resetPriorityTimer = resetPriorityTime;
102  }
103  }
104  }
105 
106  // For temporarily forcing walking. Will reset after each priority calculation, so it will need to be kept alive by something.
107  // The intention of this boolean to allow walking even when the priority is higher than AIObjectiveManager.RunPriority.
108  public bool ForceWalk { get; set; }
109 
110  public bool IgnoreAtOutpost { get; set; }
111 
112  public readonly Character character;
114  public readonly Identifier Option;
115 
116  private bool _abandon;
117  public bool Abandon
118  {
119  get { return _abandon; }
120  set
121  {
122  _abandon = value;
123  if (_abandon)
124  {
125  OnAbandon();
126  }
127  }
128  }
129 
130  public IEnumerable<AIObjective> SubObjectives => subObjectives;
131 
132  public AIObjective CurrentSubObjective => subObjectives.FirstOrDefault();
133 
134  private readonly List<AIObjective> all = new List<AIObjective>();
135 
136  public IEnumerable<AIObjective> GetSubObjectivesRecursive(bool includingSelf = false)
137  {
138  all.Clear();
139  if (includingSelf)
140  {
141  all.Add(this);
142  }
143  foreach (var subObjective in subObjectives)
144  {
145  all.AddRange(subObjective.GetSubObjectivesRecursive(true));
146  }
147  return all;
148  }
149 
153  public Func<AIObjective, bool> AbortCondition;
154 
158  public event Action Completed;
162  public event Action Abandoned;
166  public event Action Selected;
170  public event Action Deselected;
171 
175 
177  {
178  var subObjective = CurrentSubObjective;
179  return subObjective == null ? this : subObjective.GetActiveObjective();
180  }
181 
182  public AIObjective(Character character, AIObjectiveManager objectiveManager, float priorityModifier, Identifier option = default)
183  {
184  this.objectiveManager = objectiveManager;
185  this.character = character;
186  Option = option;
187  PriorityModifier = priorityModifier;
188  }
189 
193  public void TryComplete(float deltaTime)
194  {
195  if (isCompleted) { return; }
196  if (CheckState()) { return; }
197  // Not ready -> act (can't do foreach because it's possible that the collection is modified in event callbacks.
198  for (int i = 0; i < subObjectives.Count; i++)
199  {
200  subObjectives[i].TryComplete(deltaTime);
201  if (!ConcurrentObjectives) { return; }
202  }
203  Act(deltaTime);
204  }
205 
206  public void AddSubObjective(AIObjective objective, bool addFirst = false)
207  {
208  var type = objective.GetType();
209  objective.SourceObjective = this;
210  subObjectives.RemoveAll(o => o.GetType() == type);
211  if (addFirst)
212  {
213  subObjectives.Insert(0, objective);
214  }
215  else
216  {
217  subObjectives.Add(objective);
218  }
219  }
220 
221  public void RemoveSubObjective<T>(ref T objective) where T : AIObjective
222  {
223  if (objective != null)
224  {
225  if (subObjectives.Contains(objective))
226  {
227  subObjectives.Remove(objective);
228  }
229  objective = null;
230  }
231  }
232 
233  public void SortSubObjectives()
234  {
235  if (!AllowSubObjectiveSorting) { return; }
236  if (subObjectives.None()) { return; }
237  var previousSubObjective = subObjectives.First();
238  subObjectives.ForEach(so => so.GetPriority());
239  subObjectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
241  {
242  subObjectives.ForEach(so => so.SortSubObjectives());
243  }
244  else
245  {
246  var currentSubObjective = subObjectives.First();
247  if (previousSubObjective != currentSubObjective)
248  {
249  previousSubObjective.OnDeselected();
250  currentSubObjective.OnSelected();
251  }
252  currentSubObjective.SortSubObjectives();
253  }
254  }
255 
256  public bool IsAllowed
257  {
258  get
259  {
260  if (!AllowWhileHandcuffed && character.LockHands) { return false; }
261  if (!AllowOutsideSubmarine && character.Submarine == null) { return false; }
262  // Evaluate ignored at outpost first, because it has higher priority than AllowInAnySub or AllowInFriendlySubs.
263  if (IsIgnoredAtOutpost()) { return false; }
264  if (AllowInAnySub) { return true; }
265  if ((AllowInFriendlySubs && character.Submarine.TeamID == CharacterTeamType.FriendlyNPC) || character.IsEscorted) { return true; }
267  }
268  }
269 
274  public bool IsIgnoredAtOutpost()
275  {
276  if (!IgnoreAtOutpost) { return false; }
277  if (!Level.IsLoadedFriendlyOutpost) { return false; }
279  if (character.Submarine?.Info == null) { return false; }
281  }
282 
283  protected void HandleDisallowed()
284  {
285  Priority = 0;
287  {
288  // Never abandon objectives inside a friendly outpost, because otherwise we'd have to reassign most orders every round.
289  Abandon = true;
290  }
291  }
292 
293  protected virtual float GetPriority()
294  {
295  if (!IsAllowed)
296  {
298  return Priority;
299  }
300  if (objectiveManager.IsOrder(this))
301  {
303  }
304  else
305  {
307  }
308  return Priority;
309  }
310 
314  public float CalculatePriority()
315  {
316  ForceWalk = false;
317  Priority = GetPriority();
318  ForceHighestPriority = false;
319  return Priority;
320  }
321 
331  public static float GetDistanceFactor(Vector2 selfPos, Vector2 targetWorldPos, float factorAtMaxDistance, float verticalDistanceMultiplier = 3, float maxDistance = 10000.0f, float factorAtMinDistance = 1.0f)
332  {
333  float yDist = Math.Abs(selfPos.Y - targetWorldPos.Y);
334  yDist = yDist > 100 ? yDist * verticalDistanceMultiplier : 0;
335  float distance = Math.Abs(selfPos.X - targetWorldPos.X) + yDist;
336  float distanceFactor = MathHelper.Lerp(factorAtMinDistance, factorAtMaxDistance, MathUtils.InverseLerp(0, maxDistance, distance));
337  return
338  factorAtMinDistance > factorAtMaxDistance ?
339  MathHelper.Clamp(distanceFactor, factorAtMaxDistance, factorAtMinDistance) :
340  MathHelper.Clamp(distanceFactor, factorAtMinDistance, factorAtMaxDistance);
341  }
342 
352  protected float GetDistanceFactor(Vector2 targetWorldPos, float factorAtMaxDistance, float verticalDistanceMultiplier = 3, float maxDistance = 10000.0f, float factorAtMinDistance = 1.0f)
353  {
354  return GetDistanceFactor(character.WorldPosition, targetWorldPos, factorAtMaxDistance, verticalDistanceMultiplier, maxDistance, factorAtMinDistance);
355  }
356 
357  private void UpdateDevotion(float deltaTime)
358  {
359  var currentObjective = objectiveManager.CurrentObjective;
360  if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.FirstOrDefault() == this))
361  {
362  CumulatedDevotion += Devotion * deltaTime;
363  }
364  }
365 
366  public virtual bool IsDuplicate<T>(T otherObjective) where T : AIObjective => otherObjective.Option == Option;
367 
368  public virtual void Update(float deltaTime)
369  {
370  if (resetPriorityTimer > 0)
371  {
372  resetPriorityTimer -= deltaTime;
373  }
374  else
375  {
376  ForceHighestPriority = false;
377  }
379  {
380  UpdateDevotion(deltaTime);
381  }
382  subObjectives.ForEach(so => so.Update(deltaTime));
383  }
384 
388  protected virtual void SyncRemovedObjectives<T1, T2>(Dictionary<T1, T2> dictionary, IEnumerable<T1> collection) where T2 : AIObjective
389  {
390  foreach (T1 key in collection)
391  {
392  if (dictionary.TryGetValue(key, out T2 objective))
393  {
394  if (!subObjectives.Contains(objective))
395  {
396  dictionary.Remove(key);
397  }
398  }
399  }
400  }
401 
407  protected bool TryAddSubObjective<T>(ref T objective, Func<T> constructor, Action onCompleted = null, Action onAbandon = null) where T : AIObjective
408  {
409  if (objective != null)
410  {
411  // Sub objective already found, no need to do anything if it remains in the subobjectives
412  // If the sub objective is removed -> it's either completed or impossible to complete.
413  if (!subObjectives.Contains(objective))
414  {
415  objective = null;
416  }
417  return false;
418  }
419  else
420  {
421  objective = constructor();
422  if (!subObjectives.Contains(objective))
423  {
424  if (objective.AllowMultipleInstances)
425  {
426  objective.SourceObjective = this;
427  subObjectives.Add(objective);
428  }
429  else
430  {
431  AddSubObjective(objective);
432  }
433  if (onCompleted != null)
434  {
435  objective.Completed += onCompleted;
436  }
437  if (onAbandon != null)
438  {
439  objective.Abandoned += onAbandon;
440  }
441  return true;
442  }
443 #if DEBUG
444  DebugConsole.ThrowError("Attempted to add a duplicate subobjective!\n" + Environment.StackTrace.CleanupStackTrace());
445 #endif
446  return false;
447  }
448  }
449 
450  public virtual void OnSelected()
451  {
452  Reset();
453  Selected?.Invoke();
454  Selected = null;
455  }
456 
457  public virtual void OnDeselected()
458  {
459  CumulatedDevotion = 0;
460  Deselected?.Invoke();
461  Deselected = null;
462  }
463 
464  protected virtual void OnCompleted()
465  {
466  Completed?.Invoke();
467  Completed = null;
468  }
469 
470  protected virtual void OnAbandon()
471  {
472  Abandoned?.Invoke();
473  Abandoned = null;
474  }
475 
476  public virtual void Reset()
477  {
478  subObjectives.Clear();
479  isCompleted = false;
480  hasBeenChecked = false;
481  _abandon = false;
482  CumulatedDevotion = 0;
483  }
484 
485  protected abstract void Act(float deltaTime);
486 
487  private bool isCompleted;
488  private bool hasBeenChecked;
489 
490  public bool IsCompleted
491  {
492  get
493  {
494  if (!hasBeenChecked)
495  {
496  CheckState();
497  }
498  return isCompleted;
499  }
500  protected set
501  {
502  if (isCompleted == value) { return; }
503  isCompleted = value;
504  if (isCompleted)
505  {
506  OnCompleted();
507  }
508  }
509  }
510 
514  private bool Check()
515  {
516  if (isCompleted) { return true; }
517  if (AbortCondition != null && AbortCondition(this))
518  {
519  Abandon = true;
520  return false;
521  }
522  return CheckObjectiveState();
523  }
524 
528  protected abstract bool CheckObjectiveState();
529 
530  private bool CheckState()
531  {
532  hasBeenChecked = true;
533  CheckSubObjectives();
534  if (subObjectives.None() || ConcurrentObjectives)
535  {
536  if (Check())
537  {
538  IsCompleted = true;
539  }
540  }
541  return isCompleted;
542  }
543 
544  private void CheckSubObjectives()
545  {
546  for (int i = 0; i < subObjectives.Count; i++)
547  {
548  var subObjective = subObjectives[i];
549  subObjective.CheckState();
550  if (subObjective.IsCompleted)
551  {
552 #if DEBUG
553  DebugConsole.NewMessage($"{character.Name}: Removing SUBobjective {subObjective.DebugTag} of {DebugTag}, because it is completed.", Color.LightGreen);
554 #endif
555  subObjectives.Remove(subObjective);
556  }
557  else if (!subObjective.CanBeCompleted)
558  {
559 #if DEBUG
560  DebugConsole.NewMessage($"{character.Name}: Removing SUBobjective {subObjective.DebugTag} of {DebugTag}, because it cannot be completed.", Color.Red);
561 #endif
562  subObjectives.Remove(subObjective);
564  {
565  if (objectiveManager.IsOrder(this))
566  {
567  Reset();
568  }
569  else
570  {
571  Abandon = true;
572  }
573  }
574  }
575  }
576  }
577 
578  protected static bool CanPutInInventory(Character character, Item item, bool allowWearing)
579  {
580  if (item == null) { return false; }
581  bool canEquip = false;
582  if (item.AllowedSlots.Contains(InvSlotType.Any))
583  {
585  {
586  canEquip = true;
587  }
588  }
589  if (!canEquip)
590  {
591  var inv = character.Inventory;
592  foreach (var allowedSlot in item.AllowedSlots)
593  {
594  if (!allowWearing)
595  {
596  if (!allowedSlot.HasFlag(InvSlotType.RightHand) && !allowedSlot.HasFlag(InvSlotType.LeftHand))
597  {
598  continue;
599  }
600  }
601  foreach (var slotType in inv.SlotTypes)
602  {
603  if (!allowedSlot.HasFlag(slotType)) { continue; }
604  for (int i = 0; i < inv.Capacity; i++)
605  {
606  canEquip = true;
607  if (allowedSlot.HasFlag(inv.SlotTypes[i]) && inv.GetItemAt(i) != null)
608  {
609  canEquip = false;
610  break;
611  }
612  }
613  }
614  }
615  }
616  return canEquip && character.Inventory.CanBePut(item);
617  }
618 
619  protected bool CanEquip(Item item, bool allowWearing) => CanPutInInventory(character, item, allowWearing);
620  }
621 }
SteeringManager SteeringManager
Definition: AIController.cs:54
virtual bool IsDuplicate< T >(T otherObjective)
IEnumerable< AIObjective > SubObjectives
Definition: AIObjective.cs:130
virtual bool IgnoreUnsafeHulls
Definition: AIObjective.cs:17
bool IsIgnoredAtOutpost()
Returns true only when at a friendly outpost and when the order is set to be ignored there....
Definition: AIObjective.cs:274
virtual bool AbandonIfDisallowed
Should the objective abandon when it's not allowed in the current context or should it just stay inac...
Definition: AIObjective.cs:58
float CalculatePriority()
Call this only when the priority needs to be recalculated. Use the cached Priority property when you ...
Definition: AIObjective.cs:314
AIObjective CurrentSubObjective
Definition: AIObjective.cs:132
void RemoveSubObjective< T >(ref T objective)
Definition: AIObjective.cs:221
virtual bool AllowInAnySub
Definition: AIObjective.cs:50
virtual bool CanBeCompleted
Definition: AIObjective.cs:60
virtual bool KeepDivingGearOnAlsoWhenInactive
Definition: AIObjective.cs:36
AIObjective SourceObjective
Which objective (if any) created this objective. When this is a subobjective, the parent objective is...
Definition: AIObjective.cs:71
EventAction SourceEventAction
Which event action (if any) created this objective
Definition: AIObjective.cs:67
virtual bool AllowMultipleInstances
Can there be multiple objective instaces of the same type?
Definition: AIObjective.cs:28
virtual void Update(float deltaTime)
Definition: AIObjective.cs:368
IEnumerable< AIObjective > GetSubObjectivesRecursive(bool includingSelf=false)
Definition: AIObjective.cs:136
virtual void OnAbandon()
Definition: AIObjective.cs:470
float Priority
Final priority value after all calculations.
Definition: AIObjective.cs:84
Action Selected
A single shot event. Automatically cleared after launching. Use OnSelected method for implementing (i...
Definition: AIObjective.cs:166
abstract void Act(float deltaTime)
void AddSubObjective(AIObjective objective, bool addFirst=false)
Definition: AIObjective.cs:206
virtual bool AllowWhileHandcuffed
Definition: AIObjective.cs:51
readonly Character character
Definition: AIObjective.cs:112
AIObjective GetActiveObjective()
Definition: AIObjective.cs:176
Action Deselected
A single shot event. Automatically cleared after launching. Use OnDeselected method for implementing ...
Definition: AIObjective.cs:170
Func< AIObjective, bool > AbortCondition
Aborts the objective when this condition is true.
Definition: AIObjective.cs:153
void TryComplete(float deltaTime)
Makes the character act according to the objective, or according to any subobjectives that need to be...
Definition: AIObjective.cs:193
virtual float GetPriority()
Definition: AIObjective.cs:293
virtual bool KeepDivingGearOn
Definition: AIObjective.cs:35
virtual float MaxDevotion
Definition: AIObjective.cs:62
virtual bool ForceRun
Definition: AIObjective.cs:16
bool TryAddSubObjective< T >(ref T objective, Func< T > constructor, Action onCompleted=null, Action onAbandon=null)
Checks if the objective already is created and added in subobjectives. If not, creates it....
Definition: AIObjective.cs:407
bool CanEquip(Item item, bool allowWearing)
virtual void SyncRemovedObjectives< T1, T2 >(Dictionary< T1, T2 > dictionary, IEnumerable< T1 > collection)
Checks if the subobjectives in the given collection are removed from the subobjectives....
Definition: AIObjective.cs:388
IndoorsSteeringManager PathSteering
Definition: AIObjective.cs:173
virtual float Devotion
Definition: AIObjective.cs:12
HumanAIController HumanAIController
Definition: AIObjective.cs:172
readonly List< AIObjective > subObjectives
Definition: AIObjective.cs:73
virtual bool AbandonWhenCannotCompleteSubObjectives
Definition: AIObjective.cs:18
AIObjective(Character character, AIObjectiveManager objectiveManager, float priorityModifier, Identifier option=default)
Definition: AIObjective.cs:182
virtual void Reset()
Definition: AIObjective.cs:476
virtual string DebugTag
Definition: AIObjective.cs:15
Action Completed
A single shot event. Automatically cleared after launching. Use OnCompleted method for implementing (...
Definition: AIObjective.cs:158
virtual bool AllowOutsideSubmarine
Definition: AIObjective.cs:44
readonly Identifier Option
Definition: AIObjective.cs:114
virtual bool PrioritizeIfSubObjectivesActive
Definition: AIObjective.cs:23
Action Abandoned
A single shot event. Automatically cleared after launching. Use OnAbandoned method for implementing (...
Definition: AIObjective.cs:162
virtual void OnSelected()
Definition: AIObjective.cs:450
virtual void OnCompleted()
Definition: AIObjective.cs:464
virtual bool AllowInFriendlySubs
When true, the objective is allowed in the player subs (when in the same team) and on friendly outpos...
Definition: AIObjective.cs:49
static bool CanPutInInventory(Character character, Item item, bool allowWearing)
Definition: AIObjective.cs:578
virtual bool AllowAutomaticItemUnequipping
There's a separate property for diving suit and mask: KeepDivingGearOn.
Definition: AIObjective.cs:41
abstract bool CheckObjectiveState()
Should return whether the objective is completed or not.
readonly AIObjectiveManager objectiveManager
Definition: AIObjective.cs:113
virtual bool AllowSubObjectiveSorting
Should subobjectives be sorted according to their priority?
Definition: AIObjective.cs:22
float GetDistanceFactor(Vector2 targetWorldPos, float factorAtMaxDistance, float verticalDistanceMultiplier=3, float maxDistance=10000.0f, float factorAtMinDistance=1.0f)
Get a normalized value representing how close the target position is. The value is a rough estimation...
Definition: AIObjective.cs:352
virtual bool ConcurrentObjectives
Run the main objective with all subobjectives concurrently? If false, the main objective will continu...
Definition: AIObjective.cs:34
virtual void OnDeselected()
Definition: AIObjective.cs:457
abstract Identifier Identifier
Definition: AIObjective.cs:14
static float GetDistanceFactor(Vector2 selfPos, Vector2 targetWorldPos, float factorAtMaxDistance, float verticalDistanceMultiplier=3, float maxDistance=10000.0f, float factorAtMinDistance=1.0f)
Get a normalized value representing how close the target position is. The value is a rough estimation...
Definition: AIObjective.cs:331
AIObjective CurrentObjective
Includes orders.
float GetOrderPriority(AIObjective objective)
bool IsOrder(AIObjective objective)
float? WaitTimer
When set above zero, the character will stand still doing nothing until the timer runs out....
bool IsAnySlotAvailable(Item item)
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
IndoorsSteeringManager PathSteering
bool CanBePut(Item item)
Can the item be put in the inventory (i.e. is there a suitable free slot or a stack the item can be p...
IEnumerable< InvSlotType > AllowedSlots
static bool IsLoadedFriendlyOutpost
Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1). Does not take reput...