Client LuaCsForBarotrauma
AIObjectiveGoTo.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
6 
7 namespace Barotrauma
8 {
10  {
11  public override Identifier Identifier { get; set; } = "go to".ToIdentifier();
12 
13  public override bool KeepDivingGearOn => GetTargetHull() == null;
14 
15  private AIObjectiveFindDivingGear findDivingGear;
16  private readonly bool repeat;
17  //how long until the path to the target is declared unreachable
18  private float waitUntilPathUnreachable;
19  private readonly bool getDivingGearIfNeeded;
20 
24  public Func<bool> requiredCondition;
25  public Func<PathNode, bool> endNodeFilter;
26 
27  public Func<float> PriorityGetter;
28 
29  public bool IsFollowOrder;
30  public bool IsWaitOrder;
31  public bool Mimic;
32 
33  public bool SpeakIfFails { get; set; } = true;
34  public bool DebugLogWhenFails { get; set; } = true;
35  public bool UsePathingOutside { get; set; } = true;
36 
39  private float _closeEnoughMultiplier = 1;
40  public float CloseEnoughMultiplier
41  {
42  get { return _closeEnoughMultiplier; }
43  set { _closeEnoughMultiplier = Math.Max(value, 1); }
44  }
45  private float _closeEnough = 50;
46  private readonly float minDistance = 50;
47  private readonly float seekGapsInterval = 1;
48  private float seekGapsTimer;
49  private bool cantFindDivingGear;
50 
54  public float CloseEnough
55  {
56  get
57  {
58  if (IsFollowOrder && Target is Character targetCharacter && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null))
59  {
60  // Keep close when the target is going inside/outside
61  return minDistance;
62  }
63  float dist = _closeEnough * CloseEnoughMultiplier;
64  float extraMultiplier = Math.Clamp(CloseEnoughMultiplier * 0.6f, 1, 3);
66  {
67  dist += ExtraDistanceWhileSwimming * extraMultiplier;
68  }
69  if (character.CurrentHull == null)
70  {
71  dist += ExtraDistanceOutsideSub * extraMultiplier;
72  }
73  return dist;
74  }
75  set
76  {
77  _closeEnough = Math.Max(minDistance, value);
78  }
79  }
80  public bool IgnoreIfTargetDead { get; set; }
81  public bool AllowGoingOutside { get; set; }
82 
83  public bool FaceTargetOnCompleted { get; set; } = true;
84 
85  public bool AlwaysUseEuclideanDistance { get; set; } = true;
86 
90  public bool UseDistanceRelativeToAimSourcePos { get; set; } = false;
91 
92  public override bool AbandonWhenCannotCompleteSubObjectives => false;
93 
94  protected override bool AllowOutsideSubmarine => AllowGoingOutside;
95  protected override bool AllowInAnySub => true;
96 
97  public Identifier DialogueIdentifier { get; set; } = "dialogcannotreachtarget".ToIdentifier();
98  public LocalizedString TargetName { get; set; }
99 
100  public ISpatialEntity Target { get; private set; }
101 
102  public float? OverridePriority = null;
103 
104  public Func<bool> SpeakCannotReachCondition { get; set; }
105 
106  protected override float GetPriority()
107  {
108  bool isOrder = objectiveManager.IsOrder(this);
109  if (!IsAllowed)
110  {
111  Priority = 0;
112  Abandon = !isOrder;
113  return Priority;
114  }
115  if (Target == null || Target is Entity e && e.Removed)
116  {
117  Priority = 0;
118  Abandon = !isOrder;
119  }
121  {
122  Priority = 0;
123  Abandon = !isOrder;
124  }
125  else
126  {
127  if (PriorityGetter != null)
128  {
130  }
131  else if (OverridePriority.HasValue)
132  {
133  Priority = OverridePriority.Value;
134  }
135  else
136  {
137  Priority = isOrder ? objectiveManager.GetOrderPriority(this) : 10;
138  }
139  }
140  return Priority;
141  }
142 
143  private readonly float avoidLookAheadDistance = 5;
144  private readonly float pathWaitingTime = 3;
145 
146  public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)
147  : base(character, objectiveManager, priorityModifier)
148  {
149  Target = target;
150  this.repeat = repeat;
151  waitUntilPathUnreachable = pathWaitingTime;
152  this.getDivingGearIfNeeded = getDivingGearIfNeeded;
153  if (Target is Item i)
154  {
155  CloseEnough = Math.Max(CloseEnough, i.InteractDistance + Math.Max(i.Rect.Width, i.Rect.Height) / 2);
156  }
157  else if (Target is Character)
158  {
159  //if closeEnough value is given, allow setting CloseEnough as low as 50, otherwise above AIObjectiveGetItem.DefaultReach
160  CloseEnough = Math.Max(closeEnough, MathUtils.NearlyEqual(closeEnough, 0.0f) ? AIObjectiveGetItem.DefaultReach : minDistance);
161  }
162  else
163  {
164  CloseEnough = closeEnough;
165  }
166  }
167 
168  private void SpeakCannotReach()
169  {
170 #if DEBUG
171  if (DebugLogWhenFails)
172  {
173  DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
174  }
175 #endif
176  if (!character.IsOnPlayerTeam) { return; }
178  if (DialogueIdentifier == null) { return; }
179  if (!SpeakIfFails) { return; }
180  if (SpeakCannotReachCondition != null && !SpeakCannotReachCondition()) { return; }
181  LocalizedString msg = TargetName == null ?
182  TextManager.Get(DialogueIdentifier) :
183  TextManager.GetWithVariable(DialogueIdentifier, "[name]".ToIdentifier(), TargetName, formatCapitals: Target is Character ? FormatCapitals.No : FormatCapitals.Yes);
184  if (msg.IsNullOrEmpty() || !msg.Loaded) { return; }
185  character.Speak(msg.Value, identifier: DialogueIdentifier, minDurationBetweenSimilar: 20.0f);
186  }
187 
188  public void ForceAct(float deltaTime) => Act(deltaTime);
189 
190  protected override void Act(float deltaTime)
191  {
192  if (Target == null)
193  {
194  Abandon = true;
195  return;
196  }
198  {
199  // Wait
201  return;
202  }
203  character.SelectedItem = null;
205  {
207  }
208  if (Target is Entity e)
209  {
210  if (e.Removed)
211  {
212  Abandon = true;
213  return;
214  }
215  else
216  {
217  character.AIController.SelectTarget(e.AiTarget);
218  }
219  }
220  Hull targetHull = GetTargetHull();
221  if (!IsFollowOrder)
222  {
223  // Abandon if going through unsafe paths or targeting unsafe hulls.
224  bool isUnreachable = HumanAIController.UnreachableHulls.Contains(targetHull);
226  {
227  // Wait orders check this so that the bot temporarily leaves the unsafe hull.
228  // Non-orders (that are not set to ignore the unsafe hulls) abandon. In practice this means e.g. repair and clean up item subobjectives (of the looping parent objective).
229  // Other orders are only abandoned if the hull is unreachable, because the path is invalid or not found at all.
231  {
232  if (HumanAIController.UnsafeHulls.Contains(targetHull))
233  {
234  isUnreachable = true;
236  }
237  else if (PathSteering?.CurrentPath != null)
238  {
239  foreach (WayPoint wp in PathSteering.CurrentPath.Nodes)
240  {
241  if (wp.CurrentHull == null) { continue; }
242  if (HumanAIController.UnsafeHulls.Contains(wp.CurrentHull))
243  {
244  isUnreachable = true;
246  }
247  }
248  }
249  }
250  }
251  if (isUnreachable)
252  {
254  if (PathSteering?.CurrentPath != null)
255  {
257  }
258  if (repeat)
259  {
260  SpeakCannotReach();
261  }
262  else
263  {
264  Abandon = true;
265  }
266  return;
267  }
268  }
269  bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
270  bool isInside = character.CurrentHull != null;
271  bool hasOutdoorNodes = insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes;
272  if (isInside && hasOutdoorNodes && !AllowGoingOutside)
273  {
274  Abandon = true;
275  }
277  {
278  waitUntilPathUnreachable -= deltaTime;
280  {
282  if (waitUntilPathUnreachable < 0)
283  {
284  waitUntilPathUnreachable = pathWaitingTime;
285  if (repeat && !IsCompleted)
286  {
287  if (!IsDoneFollowing())
288  {
289  SpeakCannotReach();
290  }
291  }
292  else
293  {
294  Abandon = true;
295  }
296  }
297  }
298  else if (HumanAIController.HasValidPath(requireUnfinished: false))
299  {
300  waitUntilPathUnreachable = pathWaitingTime;
301  }
302  }
303  if (Abandon) { return; }
304  if (getDivingGearIfNeeded)
305  {
306  Character followTarget = Target as Character;
307  bool needsDivingSuit = (!isInside || hasOutdoorNodes) && !character.IsImmuneToPressure;
308  bool tryToGetDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
309  bool tryToGetDivingSuit = needsDivingSuit;
311  {
312  if (HumanAIController.HasDivingSuit(followTarget))
313  {
314  tryToGetDivingGear = true;
315  tryToGetDivingSuit = true;
316  }
318  {
319  tryToGetDivingGear = true;
320  }
321  }
322  bool needsEquipment = false;
324  if (tryToGetDivingSuit)
325  {
326  needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen, requireSuitablePressureProtection: !objectiveManager.FailedToFindDivingGearForDepth);
327  }
328  else if (tryToGetDivingGear)
329  {
330  needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
331  }
332  if (character.LockHands)
333  {
334  cantFindDivingGear = true;
335  }
336  if (cantFindDivingGear && needsDivingSuit)
337  {
338  // Don't try to reach the target without a suit because it's lethal.
339  Abandon = true;
340  return;
341  }
342  if (needsEquipment && !cantFindDivingGear)
343  {
345  TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: tryToGetDivingSuit, objectiveManager),
346  onAbandon: () =>
347  {
348  cantFindDivingGear = true;
349  if (needsDivingSuit)
350  {
351  // Shouldn't try to reach the target without a suit, because it's lethal.
352  Abandon = true;
353  }
354  else
355  {
356  // Try again without requiring the diving suit
357  RemoveSubObjective(ref findDivingGear);
358  TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
359  onAbandon: () =>
360  {
361  Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
362  RemoveSubObjective(ref findDivingGear);
363  },
364  onCompleted: () =>
365  {
366  RemoveSubObjective(ref findDivingGear);
367  });
368  }
369  },
370  onCompleted: () => RemoveSubObjective(ref findDivingGear));
371  return;
372  }
373  }
374  if (IsDoneFollowing())
375  {
376  OnCompleted();
377  return;
378  }
379  float maxGapDistance = 500;
380  Character targetCharacter = Target as Character;
382  {
383  if (character.CurrentHull == null ||
384  IsFollowOrder &&
385  targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
386  Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
387  {
388  if (seekGapsTimer > 0)
389  {
390  seekGapsTimer -= deltaTime;
391  }
392  else
393  {
394  bool isRuins = character.Submarine?.Info.IsRuin != null || Target.Submarine?.Info.IsRuin != null;
395  bool isEitherOneInside = isInside || Target.Submarine != null;
396  if (isEitherOneInside && (!isRuins || !HumanAIController.HasValidPath()))
397  {
398  SeekGaps(maxGapDistance);
399  seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
400  if (TargetGap != null)
401  {
402  // Check that nothing is blocking the way
403  Vector2 rayStart = character.SimPosition;
404  Vector2 rayEnd = TargetGap.SimPosition;
405  if (TargetGap.Submarine != null && character.Submarine == null)
406  {
407  rayStart -= TargetGap.Submarine.SimPosition;
408  }
409  else if (TargetGap.Submarine == null && character.Submarine != null)
410  {
411  rayEnd -= character.Submarine.SimPosition;
412  }
413  var closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
414  if (closestBody != null)
415  {
416  TargetGap = null;
417  }
418  }
419  }
420  else
421  {
422  TargetGap = null;
423  }
424  }
425  }
426  else
427  {
428  TargetGap = null;
429  }
430  if (TargetGap != null)
431  {
433  {
434  SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
435  return;
436  }
437  else
438  {
439  TargetGap = null;
440  }
441  }
442  if (checkScooterTimer <= 0)
443  {
444  useScooter = false;
445  checkScooterTimer = checkScooterTime * Rand.Range(0.75f, 1.25f);
446  Item scooter = null;
447  bool shouldUseScooter = Mimic && targetCharacter != null && targetCharacter.HasEquippedItem(Tags.Scooter, allowBroken: false);
448  if (!shouldUseScooter)
449  {
450  float threshold = 500;
451  if (isInside)
452  {
453  Vector2 diff = Target.WorldPosition - character.WorldPosition;
454  shouldUseScooter = Math.Abs(diff.X) > threshold || Math.Abs(diff.Y) > 150;
455  }
456  else
457  {
458  shouldUseScooter = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) > threshold * threshold;
459  }
460  }
461  if (HumanAIController.HasItem(character, Tags.Scooter, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
462  {
463  // Currently equipped scooter
464  scooter = equippedScooters.FirstOrDefault();
465  }
466  else if (shouldUseScooter)
467  {
468  var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand);
469  var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand);
470  bool handsFull =
471  (leftHandItem != null && !character.Inventory.IsAnySlotAvailable(leftHandItem) && !character.Inventory.TryPutItem(leftHandItem, character, InvSlotType.Bag.ToEnumerable())) ||
472  (rightHandItem != null && !character.Inventory.IsAnySlotAvailable(rightHandItem) && !character.Inventory.TryPutItem(rightHandItem, character, InvSlotType.Bag.ToEnumerable()));
473  if (!handsFull)
474  {
475  bool hasBattery = false;
476  if (HumanAIController.HasItem(character, Tags.Scooter, out IEnumerable<Item> nonEquippedScooters, containedTag: Tags.MobileBattery, conditionPercentage: 1, requireEquipped: false))
477  {
478  // Non-equipped scooter with a battery
479  scooter = nonEquippedScooters.FirstOrDefault();
480  hasBattery = true;
481  }
482  else if (HumanAIController.HasItem(character, Tags.Scooter, out IEnumerable<Item> _nonEquippedScooters, requireEquipped: false))
483  {
484  // Non-equipped scooter without a battery
485  scooter = _nonEquippedScooters.FirstOrDefault();
486  // Non-recursive so that the bots won't take batteries from other items. Also means that they can't find batteries inside containers. Not sure how to solve this.
487  hasBattery = HumanAIController.HasItem(character, Tags.MobileBattery, out _, requireEquipped: false, conditionPercentage: 1, recursive: false);
488  }
489  if (scooter != null && hasBattery)
490  {
491  // Equip only if we have a battery available
492  HumanAIController.TakeItem(scooter, character.Inventory, equip: true, dropOtherIfCannotMove: false, allowSwapping: true, storeUnequipped: false);
493  }
494  }
495  }
496  if (scooter != null && character.HasEquippedItem(scooter))
497  {
498  if (shouldUseScooter)
499  {
500  useScooter = true;
501  // Check the battery
502  if (scooter.ContainedItems.None(i => i.Condition > 0))
503  {
504  // Try to switch batteries
505  if (HumanAIController.HasItem(character, Tags.MobileBattery, out IEnumerable<Item> batteries, conditionPercentage: 1, recursive: false))
506  {
507  scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.AnySlot));
508  if (!scooter.Combine(batteries.OrderByDescending(b => b.Condition).First(), character))
509  {
510  useScooter = false;
511  }
512  }
513  else
514  {
515  useScooter = false;
516  }
517  }
518  }
519  if (!useScooter)
520  {
521  // Unequip
523  }
524  }
525  }
526  else
527  {
528  checkScooterTimer -= deltaTime;
529  }
530  }
531  else
532  {
533  TargetGap = null;
534  useScooter = false;
535  checkScooterTimer = 0;
536  }
538  {
539  Vector2 targetPos = character.GetRelativeSimPosition(Target);
540  Func<PathNode, bool> nodeFilter = null;
541  if (isInside && !AllowGoingOutside)
542  {
543  nodeFilter = n => n.Waypoint.CurrentHull != null;
544  }
545  else if (!isInside)
546  {
548  {
549  nodeFilter = n => n.Waypoint.Submarine == null;
550  }
551  else
552  {
553  nodeFilter = n => n.Waypoint.Submarine != null || n.Waypoint.Ruin != null;
554  }
555  }
556  if (!isInside && !UsePathingOutside)
557  {
561  {
562  SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
563  }
564  }
565  else
566  {
567  PathSteering.SteeringSeek(targetPos, weight: 1,
568  startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null),
570  nodeFilter: nodeFilter,
571  checkVisiblity: Target is Item || Target is Character);
572  }
574  {
575  if (useScooter)
576  {
577  UseScooter(Target.WorldPosition);
578  }
579  else
580  {
584  {
585  SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 2);
586  }
587  }
588  }
589  else if (useScooter && PathSteering.CurrentPath?.CurrentNode != null)
590  {
592  }
593  }
594  else
595  {
596  if (useScooter)
597  {
598  UseScooter(Target.WorldPosition);
599  }
600  else
601  {
605  {
606  SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
607  }
608  }
609  }
610 
611  void UseScooter(Vector2 targetWorldPos)
612  {
613  if (!character.HasEquippedItem("scooter".ToIdentifier())) { return; }
616  character.CursorPosition = targetWorldPos;
617  if (character.Submarine != null)
618  {
620  }
621  Vector2 diff = character.CursorPosition - character.Position;
622  Vector2 dir = Vector2.Normalize(diff);
623  if (character.CurrentHull == null && IsFollowOrder)
624  {
625  float sqrDist = diff.LengthSquared();
626  if (sqrDist > MathUtils.Pow2(CloseEnough * 1.5f))
627  {
628  SteeringManager.SteeringManual(1.0f, dir);
629  }
630  else
631  {
632  float dot = Vector2.Dot(dir, VectorExtensions.Forward(character.AnimController.Collider.Rotation + MathHelper.PiOver2));
633  bool isFacing = dot > 0.9f;
634  if (!isFacing && sqrDist > MathUtils.Pow2(CloseEnough))
635  {
636  SteeringManager.SteeringManual(1.0f, dir);
637  }
638  }
639  }
640  else
641  {
642  SteeringManager.SteeringManual(1.0f, dir);
643  }
644  character.SetInput(InputType.Aim, false, true);
645  character.SetInput(InputType.Shoot, false, true);
646  }
647 
648  bool IsDoneFollowing()
649  {
650  if (repeat && IsCloseEnough)
651  {
652  if (requiredCondition == null || requiredCondition())
653  {
655  {
656  return true;
657  }
658  }
659  }
660  return false;
661  }
662  }
663 
664  private bool useScooter;
665  private float checkScooterTimer;
666  private readonly float checkScooterTime = 0.5f;
667 
669 
670  public static Hull GetTargetHull(ISpatialEntity target)
671  {
672  if (target is Hull h)
673  {
674  return h;
675  }
676  else if (target is Item i)
677  {
678  return i.CurrentHull;
679  }
680  else if (target is Character c)
681  {
682  return c.CurrentHull ?? c.AnimController.CurrentHull;
683  }
684  else if (target is Structure structure)
685  {
686  return Hull.FindHull(structure.Position, useWorldCoordinates: false);
687  }
688  else if (target is Gap g)
689  {
690  return g.FlowTargetHull;
691  }
692  else if (target is WayPoint wp)
693  {
694  return wp.CurrentHull;
695  }
696  else if (target is FireSource fs)
697  {
698  return fs.Hull;
699  }
700  else if (target is OrderTarget ot)
701  {
702  return ot.Hull;
703  }
704  return null;
705  }
706 
707  public Gap TargetGap { get; private set; }
708  private void SeekGaps(float maxDistance)
709  {
710  Gap selectedGap = null;
711  float selectedDistance = -1;
712  Vector2 toTargetNormalized = Vector2.Normalize(Target.WorldPosition - character.WorldPosition);
713  foreach (Gap gap in Gap.GapList)
714  {
715  if (gap.Open < 1) { continue; }
716  if (gap.Submarine == null) { continue; }
717  if (!IsFollowOrder)
718  {
719  if (gap.FlowTargetHull == null) { continue; }
720  if (gap.Submarine != Target.Submarine) { continue; }
721  }
722  Vector2 toGap = gap.WorldPosition - character.WorldPosition;
723  if (Vector2.Dot(Vector2.Normalize(toGap), toTargetNormalized) < 0) { continue; }
724  float squaredDistance = toGap.LengthSquared();
725  if (squaredDistance > maxDistance * maxDistance) { continue; }
726  if (selectedGap == null || squaredDistance < selectedDistance)
727  {
728  selectedGap = gap;
729  selectedDistance = squaredDistance;
730  }
731  }
732  TargetGap = selectedGap;
733  }
734 
735  public bool IsCloseEnough
736  {
737  get
738  {
739  if (character.IsClimbing)
740  {
742  {
744  {
745  // The target is still above us
746  return false;
747  }
749  {
750  // Going through a hatch
751  return false;
752  }
753  }
754  }
756  {
757  float yDist = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
758  if (yDist > CloseEnough) { return false; }
759  float xDist = Math.Abs(Target.WorldPosition.X - character.WorldPosition.X);
760  return xDist <= CloseEnough;
761  }
763  return Vector2.DistanceSquared(Target.WorldPosition, sourcePos) < CloseEnough * CloseEnough;
764  }
765  }
766 
767  protected override bool CheckObjectiveSpecific()
768  {
769  if (IsCompleted) { return true; }
770  // First check the distance and then if can interact (heaviest)
771  if (Target == null)
772  {
773  Abandon = true;
774  return false;
775  }
776  if (repeat)
777  {
778  return false;
779  }
780  else
781  {
782  if (IsCloseEnough)
783  {
784  if (requiredCondition == null || requiredCondition())
785  {
786  if (Target is Item item)
787  {
788  if (character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; }
789  }
790  else if (Target is Character targetCharacter)
791  {
792  character.SelectCharacter(targetCharacter);
793  if (character.CanInteractWith(targetCharacter, skipDistanceCheck: true)) { IsCompleted = true; }
795  }
796  else
797  {
798  IsCompleted = true;
799  }
800  }
801  }
802  }
803  return IsCompleted;
804  }
805 
806  protected override void OnAbandon()
807  {
808  StopMovement();
810  {
812  }
813  SpeakCannotReach();
814  base.OnAbandon();
815  }
816 
817  private void StopMovement()
818  {
820  if (FaceTargetOnCompleted && Target is Entity { Removed: false })
821  {
823  }
824  }
825 
826  protected override void OnCompleted()
827  {
828  StopMovement();
829  if (Target is WayPoint { Ladders: null })
830  {
831  // Release ladders when ordered to wait at a spawnpoint.
832  // This is a special case specifically meant for NPCs that spawn in outposts with a wait order.
833  // Otherwise they might keep holding to the ladders when the target is just next to it.
835  {
837  }
838  }
839  base.OnCompleted();
840  }
841 
842  public override void Reset()
843  {
844  base.Reset();
845  findDivingGear = null;
846  seekGapsTimer = 0;
847  TargetGap = null;
848  if (SteeringManager is IndoorsSteeringManager pathSteering)
849  {
850  pathSteering.ResetPath();
851  }
852  }
853  }
854 }
virtual bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime)
bool TakeItem(Item item, CharacterInventory targetInventory, bool equip, bool wear=false, bool dropOtherIfCannotMove=true, bool allowSwapping=false, bool storeUnequipped=false, IEnumerable< Identifier > targetTags=null)
bool HasValidPath(bool requireNonDirty=true, bool requireUnfinished=true, Func< WayPoint, bool > nodePredicate=null)
Is the current path valid, using the provided parameters.
void FaceTarget(ISpatialEntity target)
static float GetMinOxygen(Character character)
Func< PathNode, bool > endNodeFilter
override float GetPriority()
Func< bool > requiredCondition
Doesn't allow the objective to complete if this condition is false
float CloseEnough
Display units
override Identifier Identifier
AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat=false, bool getDivingGearIfNeeded=true, float priorityModifier=1, float closeEnough=0)
void ForceAct(float deltaTime)
static Hull GetTargetHull(ISpatialEntity target)
Func< bool > SpeakCannotReachCondition
override bool CheckObjectiveSpecific()
Should return whether the objective is completed or not.
bool UseDistanceRelativeToAimSourcePos
If true, the distance to the destination is calculated from the character's AimSourcePos (= shoulder)...
override void Act(float deltaTime)
override bool AbandonWhenCannotCompleteSubObjectives
override bool AllowOutsideSubmarine
float Priority
Final priority value after all calculations.
AIObjective CurrentObjective
Includes orders.
AIObjective?? CurrentOrder
The AIObjective in CurrentOrders with the highest AIObjective.Priority
float GetOrderPriority(AIObjective objective)
bool IsOrder(AIObjective objective)
void SetInput(InputType inputType, bool hit, bool held)
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
bool CanInteractWith(Character c, float maxDist=200.0f, bool checkVisibility=true, bool skipDistanceCheck=false)
bool HasEquippedItem(Item item, InvSlotType? slotType=null, Func< InvSlotType, bool > predicate=null)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
Item GetEquippedItem(Identifier tagOrIdentifier=default, InvSlotType? slotType=null)
Vector2 GetRelativeSimPosition(ISpatialEntity target, Vector2? worldPos=null)
Item SelectedSecondaryItem
The secondary selected item. It's an item other than a device (see SelectedItem), e....
bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity=null, bool seeThroughWindows=false, bool checkFacing=false)
bool IsAnySlotAvailable(Item item)
override bool TryPutItem(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true, bool ignoreCondition=false)
If there is room, puts the item in the inventory and returns true, otherwise returns false
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
static bool HasDivingSuit(Character character, float conditionPercentage=0, bool requireOxygenTank=true, bool requireSuitablePressureProtection=true)
Check whether the character has a diving suit in usable condition, suitable pressure protection for t...
static bool HasItem(Character character, Identifier tagOrIdentifier, out IEnumerable< Item > items, Identifier containedTag=default, float conditionPercentage=0, bool requireEquipped=false, bool recursive=true, Func< Item, bool > predicate=null)
Note: uses a single list for matching items. The item is reused each time when the method is called....
static bool HasDivingGear(Character character, float conditionPercentage=0, bool requireOxygenTank=true)
void AskToRecalculateHullSafety(Hull hull)
bool UseOutsideWaypoints
Waypoints that are not linked to a sub (e.g. main path).
static bool IsFriendly(Character me, Character other, bool onlySameTeam=false)
static bool HasDivingMask(Character character, float conditionPercentage=0, bool requireOxygenTank=true)
Check whether the character has a diving mask in usable condition plus some oxygen.
void SteeringSeek(Vector2 target, float weight, float minGapWidth=0, Func< PathNode, bool > startNodeFilter=null, Func< PathNode, bool > endNodeFilter=null, Func< PathNode, bool > nodeFilter=null, bool checkVisiblity=true)
void SteeringSeekSimple(Vector2 targetSimPos, float weight=1)
bool Combine(Item item, Character user)
void SteeringManual(float deltaTime, Vector2 velocity)
void SteeringSeek(Vector2 targetSimPos, float weight=1)
void SteeringAvoid(float deltaTime, float lookAheadDistance, float weight=1)
List< WayPoint > Nodes
static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel=false, bool ignoreSubs=false, bool ignoreSensors=true, bool ignoreDisabledWalls=true, bool ignoreBranches=true, Predicate< Fixture > blocksVisibilityPredicate=null)
Check visibility between two points (in sim units).