Client LuaCsForBarotrauma
AIObjectiveGetItem.cs
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Immutable;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Diagnostics;
9 
10 namespace Barotrauma
11 {
13  {
14  public override Identifier Identifier { get; set; } = "get item".ToIdentifier();
15 
16  public override bool AbandonWhenCannotCompleteSubObjectives => false;
17  public override bool AllowMultipleInstances => true;
18  protected override bool AllowWhileHandcuffed => false;
19 
20  public HashSet<Item> ignoredItems = new HashSet<Item>();
21 
22  public Func<Item, float> GetItemPriority;
23  public Func<Item, bool> ItemFilter;
24  public float TargetCondition { get; set; } = 1;
25  public bool AllowDangerousPressure { get; set; }
26 
27  public readonly ImmutableHashSet<Identifier> IdentifiersOrTags;
28 
29  //if the item can't be found, spawn it in the character's inventory (used by outpost NPCs)
30  private readonly bool spawnItemIfNotFound = false;
31 
32  private Item targetItem;
33  private readonly Item originalTarget;
34  private ISpatialEntity moveToTarget;
35  private bool isDoneSeeking;
36  public Item TargetItem => targetItem;
37  private int currentSearchIndex;
38  public ImmutableHashSet<Identifier> ignoredContainerIdentifiers;
39  public ImmutableHashSet<Identifier> ignoredIdentifiersOrTags;
40  private AIObjectiveGoTo goToObjective;
41  private float currItemPriority;
42  private readonly bool checkInventory;
43 
44  public const float DefaultReach = 100;
45  public const float MaxReach = 150;
46 
47  public bool AllowToFindDivingGear { get; set; } = true;
48  public bool MustBeSpecificItem { get; set; }
49 
53  public bool AllowStealing { get; set; }
54  public bool TakeWholeStack { get; set; }
58  public bool AllowVariants { get; set; }
59  public bool Equip { get; set; }
60  public bool Wear { get; set; }
61  public bool RequireNonEmpty { get; set; }
62  public bool EvaluateCombatPriority { get; set; }
63  public bool CheckPathForEachItem { get; set; }
64  public bool SpeakIfFails { get; set; }
65  public string CannotFindDialogueIdentifierOverride { get; set; }
66  public Func<bool> CannotFindDialogueCondition { get; set; }
67 
68  private int _itemCount = 1;
69  public int ItemCount
70  {
71  get { return _itemCount; }
72  set
73  {
74  _itemCount = Math.Max(value, 1);
75  }
76  }
77 
78  public InvSlotType? EquipSlotType { get; set; }
79 
80  public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
81  : base(character, objectiveManager, priorityModifier)
82  {
83  currentSearchIndex = 0;
84  Equip = equip;
85  originalTarget = targetItem;
86  this.targetItem = targetItem;
87  moveToTarget = targetItem?.GetRootInventoryOwner();
88  }
89 
90  public AIObjectiveGetItem(Character character, Identifier identifierOrTag, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
91  : this(character, new Identifier[] { identifierOrTag }, objectiveManager, equip, checkInventory, priorityModifier, spawnItemIfNotFound) { }
92 
93  public AIObjectiveGetItem(Character character, IEnumerable<Identifier> identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
94  : base(character, objectiveManager, priorityModifier)
95  {
96  currentSearchIndex = 0;
97  Equip = equip;
98  this.spawnItemIfNotFound = spawnItemIfNotFound;
99  this.checkInventory = checkInventory;
100  IdentifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableHashSet();
101  ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToImmutableHashSet();
102  }
103 
104  public static IEnumerable<Identifier> ParseGearTags(IEnumerable<Identifier> identifiersOrTags)
105  {
106  var tags = new List<Identifier>();
107  foreach (Identifier tag in identifiersOrTags)
108  {
109  if (!tag.Contains("!"))
110  {
111  tags.Add(tag);
112  }
113  }
114  return tags;
115  }
116 
117  public static IEnumerable<Identifier> ParseIgnoredTags(IEnumerable<Identifier> identifiersOrTags)
118  {
119  var ignoredTags = new List<Identifier>();
120  foreach (Identifier tag in identifiersOrTags)
121  {
122  if (tag.Contains("!"))
123  {
124  ignoredTags.Add(tag.Remove("!"));
125  }
126  }
127  return ignoredTags;
128  }
129 
130  public static Func<PathNode, bool> CreateEndNodeFilter(ISpatialEntity targetEntity)
131  {
132  return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(MaxReach);
133  }
134 
135  private bool CheckInventory()
136  {
137  if (IdentifiersOrTags == null) { return false; }
138  var item = character.Inventory.FindItem(i => CheckItem(i), recursive: true);
139  if (item != null)
140  {
141  targetItem = item;
142  moveToTarget = item.GetRootInventoryOwner();
143  }
144  return item != null;
145  }
146 
147  private bool CountItems()
148  {
149  int itemCount = 0;
150  foreach (Item it in character.Inventory.AllItems)
151  {
152  if (CheckItem(it))
153  {
154  itemCount++;
155  }
156  }
157  return itemCount >= ItemCount;
158  }
159 
160  protected override void Act(float deltaTime)
161  {
162  if (IdentifiersOrTags != null)
163  {
164  if (checkInventory)
165  {
166  if (CheckInventory())
167  {
168  isDoneSeeking = true;
169  itemCandidates.Clear();
170  }
171  }
172  if (!isDoneSeeking)
173  {
174  if (character.Submarine == null)
175  {
176  Abandon = true;
177  return;
178  }
180  {
181  bool dangerousPressure = !character.IsProtectedFromPressure && (character.CurrentHull == null || character.CurrentHull.LethalPressure > 0);
182  if (dangerousPressure)
183  {
184 #if DEBUG
185  string itemName = targetItem != null ? targetItem.Name : IdentifiersOrTags.FirstOrDefault().Value;
186  DebugConsole.NewMessage($"{character.Name}: Seeking item ({itemName}) aborted, because the pressure is dangerous.", Color.Yellow);
187 #endif
188  Abandon = true;
189  return;
190  }
191  }
192  FindTargetItem();
193  }
194  if (targetItem == null)
195  {
196  if (isDoneSeeking)
197  {
198  HandlePotentialItems();
199  }
201  {
202  objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
203  }
204  return;
205  }
206  }
207  else if (character.Submarine == null)
208  {
209  Abandon = true;
210  return;
211  }
212  bool ShouldAbort() => IdentifiersOrTags is null || isDoneSeeking && itemCandidates.None();
213  if (targetItem is null or { Removed: true })
214  {
215  if (ShouldAbort())
216  {
217 #if DEBUG
218  DebugConsole.NewMessage($"{character.Name}: Target null or removed. Aborting.", Color.Red);
219 #endif
220  Abandon = true;
221  }
222  return;
223  }
224  if (moveToTarget is null)
225  {
226  if (ShouldAbort())
227  {
228 #if DEBUG
229  DebugConsole.NewMessage($"{character.Name}: Move target null. Aborting.", Color.Red);
230 #endif
231  Abandon = true;
232  return;
233  }
234  return;
235  }
236  if (character.IsItemTakenBySomeoneElse(targetItem))
237  {
238 #if DEBUG
239  DebugConsole.NewMessage($"{character.Name}: Found an item, but it's already equipped by someone else.", Color.Yellow);
240 #endif
241  if (originalTarget == null)
242  {
243  // Try again
244  ignoredItems.Add(targetItem);
245  ResetInternal();
246  }
247  else
248  {
249  Abandon = true;
250  }
251  return;
252  }
253  bool canInteract = false;
254  if (moveToTarget is Character c)
255  {
256  if (character == c)
257  {
258  canInteract = true;
259  moveToTarget = null;
260  }
261  else
262  {
264  canInteract = character.CanInteractWith(c);
266  }
267  }
268  else if (moveToTarget is Item parentItem)
269  {
270  canInteract = character.CanInteractWith(parentItem, checkLinked: false);
271  }
272  if (canInteract)
273  {
274  var pickable = targetItem.GetComponent<Pickable>();
275  if (pickable == null)
276  {
277 #if DEBUG
278  DebugConsole.NewMessage($"{character.Name}: Target not pickable. Aborting.", Color.Yellow);
279 #endif
280  Abandon = true;
281  return;
282  }
283 
284  Inventory itemInventory = targetItem.ParentInventory;
285  var slots = itemInventory?.FindIndices(targetItem);
286  var droppedStack = TargetItem.DroppedStack.ToList();
287  if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true, targetTags: IdentifiersOrTags))
288  {
289  if (TakeWholeStack)
290  {
291  //taking the whole stack in this context means "as many items that can fit in one of the bot's slots",
292  //and the stack means either a stack of items in an inventory slot or a "dropped stack"
293  //so we need a bit of extra logic here
294  int maxStackSize = 0;
295  int takenItemCount = 1;
296  for (int i = 0; i < character.Inventory.Capacity; i++)
297  {
298  maxStackSize = Math.Max(maxStackSize, character.Inventory.HowManyCanBePut(targetItem.Prefab, i, condition: null));
299  }
300  if (slots != null)
301  {
302  foreach (int slot in slots)
303  {
304  foreach (Item item in itemInventory.GetItemsAt(slot).ToList())
305  {
306  if (HumanAIController.TakeItem(item, character.Inventory, equip: false, storeUnequipped: true))
307  {
308  takenItemCount++;
309  if (takenItemCount >= maxStackSize) { break; }
310  }
311  else
312  {
313  break;
314  }
315  }
316  }
317  }
318  foreach (var item in droppedStack)
319  {
320  if (item == TargetItem) { continue; }
321  if (HumanAIController.TakeItem(item, character.Inventory, equip: false, storeUnequipped: true))
322  {
323  takenItemCount++;
324  if (takenItemCount >= maxStackSize) { break; }
325  }
326  else
327  {
328  break;
329  }
330  }
331  }
332  if (IdentifiersOrTags == null)
333  {
334  IsCompleted = true;
335  }
336  else
337  {
338  IsCompleted = CountItems();
339  if (!IsCompleted)
340  {
341  ResetInternal();
342  }
343  }
344  }
345  else
346  {
347  if (!Equip)
348  {
349  // Try equipping and wearing the item
350  Equip = true;
351  if (!objectiveManager.HasActiveObjective<AIObjectiveCleanupItem>() && !objectiveManager.HasActiveObjective<AIObjectiveLoadItem>())
352  {
353  Wear = true;
354  }
355  return;
356  }
357 #if DEBUG
358  DebugConsole.NewMessage($"{character.Name}: Failed to equip/move the item '{targetItem.Name}' into the character inventory. Aborting.", Color.Red);
359 #endif
360  Abandon = true;
361  }
362  }
363  else if (moveToTarget != null)
364  {
365  TryAddSubObjective(ref goToObjective,
366  constructor: () =>
367  {
368  return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach)
369  {
370  // If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
371  AbortCondition = obj => targetItem == null || (targetItem.GetRootInventoryOwner() is Entity owner && owner != moveToTarget && owner != character),
372  SpeakIfFails = false,
373  endNodeFilter = CreateEndNodeFilter(moveToTarget)
374  };
375  },
376  onAbandon: () =>
377  {
378  if (originalTarget == null)
379  {
380  // Try again
381  ignoredItems.Add(targetItem);
382  if (targetItem != moveToTarget && moveToTarget is Item item)
383  {
384  ignoredItems.Add(item);
385  }
386  ResetInternal();
387  }
388  else
389  {
390  Abandon = true;
391  }
392  },
393  onCompleted: () => RemoveSubObjective(ref goToObjective));
394  }
395  }
396 
397  private Stopwatch sw;
398  private Stopwatch StopWatch => sw ??= new Stopwatch();
399  private readonly List<(Item item, float priority)> itemCandidates = new List<(Item, float)>();
400  private List<Item> itemList;
401  private void FindTargetItem()
402  {
403  if (IdentifiersOrTags == null)
404  {
405  if (targetItem == null)
406  {
407 #if DEBUG
408  DebugConsole.AddWarning($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.");
409 #endif
410  Abandon = true;
411  }
412  return;
413  }
415  {
416  StopWatch.Restart();
417  }
418  float priority = objectiveManager.GetCurrentPriority();
419  bool checkPath = CheckPathForEachItem || priority >= AIObjectiveManager.RunPriority || ItemCount > 1;
420  // Reset if the character has switched subs.
421  if (itemList != null && !character.Submarine.IsEntityFoundOnThisSub(itemList.FirstOrDefault(), includingConnectedSubs: true))
422  {
423  currentSearchIndex = 0;
424  }
425  if (currentSearchIndex == 0)
426  {
427  itemCandidates.Clear();
428  itemList = character.Submarine.GetItems(alsoFromConnectedSubs: true);
429  }
430  int itemsPerFrame = (int)MathHelper.Lerp(30, 300, MathUtils.InverseLerp(10, 100, priority));
431  int checkedItems = 0;
432  for (int i = 0; i < itemsPerFrame && currentSearchIndex < itemList.Count; i++, currentSearchIndex++)
433  {
434  checkedItems++;
435  var item = itemList[currentSearchIndex];
436  Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
437  if (itemSub == null) { continue; }
438  Submarine mySub = character.Submarine;
439  if (mySub == null) { continue; }
440  if (!checkInventory)
441  {
442  // Ignore items in the inventory when defined not to check it.
443  if (item.IsOwnedBy(character)) { continue; }
444  }
446  {
447  if (item.Illegitimate) { continue; }
448  }
449  if (!CheckItem(item)) { continue; }
450  if (item.Container != null)
451  {
452  if (item.Container.HasTag(Tags.DontTakeItems)) { continue; }
453  if (ignoredItems.Contains(item.Container)) { continue; }
454  if (ignoredContainerIdentifiers != null)
455  {
456  if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
457  }
458  }
459  if (character.IsItemTakenBySomeoneElse(item)) { continue; }
460  if (item.ParentInventory is ItemInventory itemInventory)
461  {
462  if (!itemInventory.Container.HasRequiredItems(character, addMessage: false)) { continue; }
463  }
464  float itemPriority = item.Prefab.BotPriority;
465  if (GetItemPriority != null)
466  {
467  itemPriority *= GetItemPriority(item);
468  }
469  if (itemPriority <= 0) { continue; }
470  Entity rootInventoryOwner = item.GetRootInventoryOwner();
471  if (rootInventoryOwner is Item ownerItem)
472  {
473  if (!ownerItem.IsInteractable(character)) { continue; }
474  if (ownerItem != item)
475  {
476  if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
477  //the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
478  if (ownerItem != item.Container)
479  {
480  itemPriority *= 0.1f;
481  }
482  }
483  }
484  Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
485  float distanceFactor =
487  itemPos,
488  verticalDistanceMultiplier: 5,
489  maxDistance: 10000,
490  factorAtMinDistance: 1.0f,
491  factorAtMaxDistance: EvaluateCombatPriority ? 0.1f : 0);
492  itemPriority *= distanceFactor;
494  {
495  var mw = item.GetComponent<MeleeWeapon>();
496  var rw = item.GetComponent<RangedWeapon>();
497  float combatFactor = 0;
498  if (mw != null)
499  {
500  if (mw.CombatPriority > 0)
501  {
502  combatFactor = mw.CombatPriority / 100;
503  }
504  else
505  {
506  // The combat factor of items with zero combat priority is not allowed to be greater than 0.1f
507  combatFactor = Math.Min(AIObjectiveCombat.GetLethalDamage(mw) / 1000, 0.1f);
508  }
509  }
510  else if (rw != null)
511  {
512  if (rw.CombatPriority > 0)
513  {
514  combatFactor = rw.CombatPriority / 100;
515  }
516  else
517  {
518  combatFactor = Math.Min(AIObjectiveCombat.GetLethalDamage(rw) / 1000, 0.1f);
519  }
520  }
521  else
522  {
523  combatFactor = Math.Min(item.Components.Sum(AIObjectiveCombat.GetLethalDamage) / 1000, 0.1f);
524  }
525  itemPriority *= combatFactor;
526  }
527  else
528  {
529  itemPriority *= item.Condition / item.MaxCondition;
530  }
531  // Ignore if the item has a lower priority than the currently selected one
532  if (itemPriority < currItemPriority) { continue; }
533  if (EvaluateCombatPriority && itemPriority <= 0)
534  {
535  // Not good enough
536  continue;
537  }
538  if (checkPath)
539  {
540  itemCandidates.Add((item, itemPriority));
541  }
542  else
543  {
544  currItemPriority = itemPriority;
545  targetItem = item;
546  moveToTarget = rootInventoryOwner ?? item;
547  }
548  }
549  if (currentSearchIndex >= itemList.Count - 1)
550  {
551  isDoneSeeking = true;
552  if (itemCandidates.Any())
553  {
554  itemCandidates.Sort((x, y) => y.priority.CompareTo(x.priority));
555  }
556  if (HumanAIController.DebugAI && StopWatch.ElapsedMilliseconds > 2)
557  {
558  string msg = $"Went through {checkedItems} of total {itemList.Count} items. Found item {targetItem?.Name ?? "NULL"} in {StopWatch.ElapsedMilliseconds} ms. Completed: {isDoneSeeking}";
559  if (StopWatch.ElapsedMilliseconds > 5)
560  {
561  DebugConsole.ThrowError(msg);
562  }
563  else
564  {
565  // An occasional warning now and then can be ignored, but multiple at the same time might indicate a performance issue.
566  DebugConsole.AddWarning(msg);
567  }
568  }
569  }
570  }
571 
572  private void HandlePotentialItems()
573  {
574  Debug.Assert(isDoneSeeking);
575  if (itemCandidates.Any())
576  {
577  if (PathSteering == null)
578  {
579  itemCandidates.Clear();
580  Abandon = true;
581  return;
582  }
583  if (itemCandidates.FirstOrDefault() is var itemCandidate)
584  {
585  var path = PathSteering.PathFinder.FindPath(character.SimPosition, character.GetRelativeSimPosition(itemCandidate.item), character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
586  if (path.Unreachable)
587  {
588  // Remove the invalid candidates and continue on the next frame.
589  itemCandidates.Remove(itemCandidate);
590  }
591  else
592  {
593  // The path was valid -> we are done.
594  itemCandidates.Clear();
595  targetItem = itemCandidate.item;
596  moveToTarget = targetItem.GetRootInventoryOwner() ?? targetItem;
597  }
598  }
599  }
600  if (targetItem == null)
601  {
602  if (spawnItemIfNotFound)
603  {
604  ItemPrefab prefab = FindItemToSpawn();
605  if (prefab == null)
606  {
607 #if DEBUG
608  DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
609 #endif
610  Abandon = true;
611  }
612  else
613  {
614  Entity.Spawner.AddItemToSpawnQueue(prefab, character.Inventory, onSpawned: (Item spawnedItem) =>
615  {
616  targetItem = spawnedItem;
617  if (character.TeamID == CharacterTeamType.FriendlyNPC && (character.Submarine?.Info.IsOutpost ?? false))
618  {
619  spawnedItem.SpawnedInCurrentOutpost = true;
620  }
621  });
622  }
623  }
624  else
625  {
626 #if DEBUG
627  DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}", Color.Yellow);
628 #endif
629  Abandon = true;
630  }
631  }
632  }
633 
639  private ItemPrefab FindItemToSpawn()
640  {
641  ItemPrefab bestItem = null;
642  float lowestCost = float.MaxValue;
643  foreach (MapEntityPrefab prefab in MapEntityPrefab.List)
644  {
645  if (prefab is not ItemPrefab itemPrefab) { continue; }
646  if (IdentifiersOrTags.Any(id => id == prefab.Identifier || prefab.Tags.Contains(id)))
647  {
648  float cost = itemPrefab.DefaultPrice != null && itemPrefab.CanBeBought ?
649  itemPrefab.DefaultPrice.Price :
650  float.MaxValue;
651  if (cost < lowestCost || bestItem == null)
652  {
653  bestItem = itemPrefab;
654  lowestCost = cost;
655  }
656  }
657  }
658  return bestItem;
659  }
660 
661  protected override bool CheckObjectiveSpecific()
662  {
663  if (IsCompleted) { return true; }
664  if (targetItem == null)
665  {
666  // Not yet ready
667  return false;
668  }
669  if (IdentifiersOrTags != null && ItemCount > 1)
670  {
671  return CountItems();
672  }
673  else
674  {
675  if (Equip && EquipSlotType.HasValue)
676  {
677  return character.HasEquippedItem(targetItem, EquipSlotType.Value);
678  }
679  else
680  {
681  return character.HasItem(targetItem, Equip);
682  }
683  }
684  }
685 
686  private bool CheckItem(Item item)
687  {
688  if (!item.HasAccess(character)) { return false; }
689  if (ignoredItems.Contains(item)) { return false; };
690  if (ignoredIdentifiersOrTags != null && item.HasIdentifierOrTags(ignoredIdentifiersOrTags)) { return false; }
691  if (item.Condition < TargetCondition) { return false; }
692  if (ItemFilter != null && !ItemFilter(item)) { return false; }
693  if (RequireNonEmpty && item.Components.Any(i => i.IsEmpty(character))) { return false; }
694  return item.HasIdentifierOrTags(IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
695  }
696 
697  public override void Reset()
698  {
699  base.Reset();
700  ResetInternal();
701  }
702 
706  private void ResetInternal()
707  {
708  RemoveSubObjective(ref goToObjective);
709  targetItem = originalTarget;
710  moveToTarget = targetItem?.GetRootInventoryOwner();
711  isDoneSeeking = false;
712  currentSearchIndex = 0;
713  currItemPriority = 0;
714  }
715 
716  protected override void OnAbandon()
717  {
718  base.OnAbandon();
719  if (moveToTarget != null)
720  {
721 #if DEBUG
722  DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
723 #endif
724  }
725  SpeakCannotFind();
726  }
727 
728  private void SpeakCannotFind()
729  {
730  if (!SpeakIfFails) { return; }
731  if (!character.IsOnPlayerTeam) { return; }
732  if (objectiveManager.CurrentOrder != objectiveManager.CurrentObjective) { return; }
733  if (CannotFindDialogueCondition != null && !CannotFindDialogueCondition()) { return; }
734  LocalizedString msg = TextManager.Get(CannotFindDialogueIdentifierOverride, "dialogcannotfinditem");
735  if (msg.IsNullOrEmpty() || !msg.Loaded) { return; }
736  character.Speak(msg.Value, identifier: "dialogcannotfinditem".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
737  }
738  }
739 }
bool TakeItem(Item item, CharacterInventory targetInventory, bool equip, bool wear=false, bool dropOtherIfCannotMove=true, bool allowSwapping=false, bool storeUnequipped=false, IEnumerable< Identifier > targetTags=null)
ImmutableHashSet< Identifier > ignoredContainerIdentifiers
AIObjectiveGetItem(Character character, Identifier identifierOrTag, AIObjectiveManager objectiveManager, bool equip=true, bool checkInventory=true, float priorityModifier=1, bool spawnItemIfNotFound=false)
override bool AbandonWhenCannotCompleteSubObjectives
bool AllowVariants
Are variants of the specified item allowed
AIObjectiveGetItem(Character character, IEnumerable< Identifier > identifiersOrTags, AIObjectiveManager objectiveManager, bool equip=true, bool checkInventory=true, float priorityModifier=1, bool spawnItemIfNotFound=false)
ImmutableHashSet< Identifier > ignoredIdentifiersOrTags
override bool CheckObjectiveSpecific()
Should return whether the objective is completed or not.
AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip=true, float priorityModifier=1)
override void Act(float deltaTime)
Func< Item, float > GetItemPriority
static IEnumerable< Identifier > ParseIgnoredTags(IEnumerable< Identifier > identifiersOrTags)
static Func< PathNode, bool > CreateEndNodeFilter(ISpatialEntity targetEntity)
bool AllowStealing
Is the character allowed to take the item from somewhere else than their own sub (e....
readonly ImmutableHashSet< Identifier > IdentifiersOrTags
static IEnumerable< Identifier > ParseGearTags(IEnumerable< Identifier > identifiersOrTags)
Func< AIObjective, bool > AbortCondition
Aborts the objective when this condition is true.
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...
float GetCurrentPriority()
Returns the highest priority of the current objective and its subobjectives.
AIObjective?? CurrentOrder
The AIObjective in CurrentOrders with the highest AIObjective.Priority
bool IsItemTakenBySomeoneElse(Item item)
bool CanInteractWith(Character c, float maxDist=200.0f, bool checkVisibility=true, bool skipDistanceCheck=false)
Vector2 GetRelativeSimPosition(ISpatialEntity target, Vector2? worldPos=null)
bool IsProtectedFromPressure
Is the character currently protected from the pressure by immunity/ability or a status effect (e....
Submarine Submarine
Definition: Entity.cs:53
List< int > FindIndices(Item item)
Find the indices of all the slots the item is contained in (two-hand items for example can be in mult...
Item FindItem(Func< Item, bool > predicate, bool recursive)
int HowManyCanBePut(ItemPrefab itemPrefab, float? condition=null)
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
IEnumerable< Item > GetItemsAt(int index)
Get all the item stored in the specified inventory slot. Can return more than one item if the slot co...
bool HasIdentifierOrTags(IEnumerable< Identifier > identifiersOrTags)
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
bool HasAccess(Character character)
Used by the AI to check whether they can (in principle) and are allowed (in practice) to interact wit...
override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg=null)
SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub=null, string errorMsgStr=null, float minGapSize=0, Func< PathNode, bool > startNodeFilter=null, Func< PathNode, bool > endNodeFilter=null, Func< PathNode, bool > nodeFilter=null, bool checkVisibility=true)
Definition: PathFinder.cs:173
List< Item > GetItems(bool alsoFromConnectedSubs)
bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam=false, bool allowDifferentType=false)