3 using Microsoft.Xna.Framework;
5 using System.Collections.Immutable;
6 using System.Collections.Generic;
8 using System.Diagnostics;
30 private readonly
bool spawnItemIfNotFound =
false;
32 private Item targetItem;
33 private readonly
Item originalTarget;
35 private bool isDoneSeeking;
37 private int currentSearchIndex;
41 private float currItemPriority;
42 private readonly
bool checkInventory;
60 public bool Wear {
get;
set; }
68 private int _itemCount = 1;
71 get {
return _itemCount; }
74 _itemCount = Math.Max(value, 1);
83 currentSearchIndex = 0;
85 originalTarget = targetItem;
86 this.targetItem = targetItem;
96 currentSearchIndex = 0;
98 this.spawnItemIfNotFound = spawnItemIfNotFound;
99 this.checkInventory = checkInventory;
104 public static IEnumerable<Identifier>
ParseGearTags(IEnumerable<Identifier> identifiersOrTags)
106 var tags =
new List<Identifier>();
109 if (!tag.Contains(
"!"))
117 public static IEnumerable<Identifier>
ParseIgnoredTags(IEnumerable<Identifier> identifiersOrTags)
119 var ignoredTags =
new List<Identifier>();
122 if (tag.Contains(
"!"))
124 ignoredTags.Add(tag.Remove(
"!"));
132 return n => (n.Waypoint.Ladders ==
null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.
WorldPosition) <= MathUtils.Pow2(
MaxReach);
135 private bool CheckInventory()
142 moveToTarget = item.GetRootInventoryOwner();
147 private bool CountItems()
160 protected override void Act(
float deltaTime)
166 if (CheckInventory())
168 isDoneSeeking =
true;
169 itemCandidates.Clear();
182 if (dangerousPressure)
186 DebugConsole.NewMessage($
"{character.Name}: Seeking item ({itemName}) aborted, because the pressure is dangerous.", Color.Yellow);
194 if (targetItem ==
null)
198 HandlePotentialItems();
212 bool ShouldAbort() =>
IdentifiersOrTags is
null || isDoneSeeking && itemCandidates.None();
213 if (targetItem is
null or { Removed:
true })
218 DebugConsole.NewMessage($
"{character.Name}: Target null or removed. Aborting.", Color.Red);
224 if (moveToTarget is
null)
229 DebugConsole.NewMessage($
"{character.Name}: Move target null. Aborting.", Color.Red);
239 DebugConsole.NewMessage($
"{character.Name}: Found an item, but it's already equipped by someone else.", Color.Yellow);
241 if (originalTarget ==
null)
253 bool canInteract =
false;
268 else if (moveToTarget is
Item parentItem)
274 var pickable = targetItem.GetComponent<
Pickable>();
275 if (pickable ==
null)
278 DebugConsole.NewMessage($
"{character.Name}: Target not pickable. Aborting.", Color.Yellow);
285 var slots = itemInventory?.
FindIndices(targetItem);
294 int maxStackSize = 0;
295 int takenItemCount = 1;
302 foreach (
int slot
in slots)
309 if (takenItemCount >= maxStackSize) {
break; }
318 foreach (var item
in droppedStack)
324 if (takenItemCount >= maxStackSize) {
break; }
358 DebugConsole.NewMessage($
"{character.Name}: Failed to equip/move the item '{targetItem.Name}' into the character inventory. Aborting.", Color.Red);
363 else if (moveToTarget !=
null)
365 TryAddSubObjective(ref goToObjective,
378 if (originalTarget ==
null)
382 if (targetItem != moveToTarget && moveToTarget is
Item item)
393 onCompleted: () => RemoveSubObjective(ref goToObjective));
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()
405 if (targetItem ==
null)
408 DebugConsole.AddWarning($
"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.");
423 currentSearchIndex = 0;
425 if (currentSearchIndex == 0)
427 itemCandidates.Clear();
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++)
435 var item = itemList[currentSearchIndex];
436 Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
437 if (itemSub ==
null) {
continue; }
439 if (mySub ==
null) {
continue; }
443 if (item.IsOwnedBy(
character)) {
continue; }
447 if (item.Illegitimate) {
continue; }
449 if (!CheckItem(item)) {
continue; }
450 if (item.Container !=
null)
452 if (item.Container.HasTag(Tags.DontTakeItems)) {
continue; }
453 if (
ignoredItems.Contains(item.Container)) {
continue; }
460 if (item.ParentInventory is ItemInventory itemInventory)
462 if (!itemInventory.Container.HasRequiredItems(
character, addMessage:
false)) {
continue; }
464 float itemPriority = item.Prefab.BotPriority;
469 if (itemPriority <= 0) {
continue; }
470 Entity rootInventoryOwner = item.GetRootInventoryOwner();
471 if (rootInventoryOwner is Item ownerItem)
473 if (!ownerItem.IsInteractable(
character)) {
continue; }
474 if (ownerItem != item)
478 if (ownerItem != item.Container)
480 itemPriority *= 0.1f;
484 Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
485 float distanceFactor =
488 verticalDistanceMultiplier: 5,
490 factorAtMinDistance: 1.0f,
492 itemPriority *= distanceFactor;
497 float combatFactor = 0;
500 if (mw.CombatPriority > 0)
507 combatFactor = Math.Min(AIObjectiveCombat.GetLethalDamage(mw) / 1000, 0.1f);
512 if (rw.CombatPriority > 0)
514 combatFactor = rw.CombatPriority / 100;
518 combatFactor = Math.Min(AIObjectiveCombat.GetLethalDamage(rw) / 1000, 0.1f);
523 combatFactor = Math.Min(item.Components.Sum(AIObjectiveCombat.GetLethalDamage) / 1000, 0.1f);
525 itemPriority *= combatFactor;
529 itemPriority *= item.Condition / item.MaxCondition;
532 if (itemPriority < currItemPriority) {
continue; }
540 itemCandidates.Add((item, itemPriority));
544 currItemPriority = itemPriority;
546 moveToTarget = rootInventoryOwner ?? item;
549 if (currentSearchIndex >= itemList.Count - 1)
551 isDoneSeeking =
true;
552 if (itemCandidates.Any())
554 itemCandidates.Sort((x, y) => y.priority.CompareTo(x.priority));
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)
561 DebugConsole.ThrowError(msg);
566 DebugConsole.AddWarning(msg);
572 private void HandlePotentialItems()
574 Debug.Assert(isDoneSeeking);
575 if (itemCandidates.Any())
579 itemCandidates.Clear();
583 if (itemCandidates.FirstOrDefault() is var itemCandidate)
586 if (path.Unreachable)
589 itemCandidates.Remove(itemCandidate);
594 itemCandidates.Clear();
595 targetItem = itemCandidate.item;
600 if (targetItem ==
null)
602 if (spawnItemIfNotFound)
604 ItemPrefab prefab = FindItemToSpawn();
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);
614 Entity.Spawner.AddItemToSpawnQueue(prefab,
character.
Inventory, onSpawned: (Item spawnedItem) =>
616 targetItem = spawnedItem;
617 if (character.TeamID == CharacterTeamType.FriendlyNPC && (character.Submarine?.Info.IsOutpost ?? false))
619 spawnedItem.SpawnedInCurrentOutpost = true;
627 DebugConsole.NewMessage($
"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(",
", IdentifiersOrTags)}", Color.Yellow);
639 private ItemPrefab FindItemToSpawn()
641 ItemPrefab bestItem =
null;
642 float lowestCost =
float.MaxValue;
643 foreach (MapEntityPrefab prefab
in MapEntityPrefab.List)
645 if (prefab is not ItemPrefab itemPrefab) {
continue; }
646 if (IdentifiersOrTags.Any(
id =>
id == prefab.Identifier || prefab.Tags.Contains(
id)))
648 float cost = itemPrefab.DefaultPrice !=
null && itemPrefab.CanBeBought ?
649 itemPrefab.DefaultPrice.Price :
651 if (cost < lowestCost || bestItem ==
null)
653 bestItem = itemPrefab;
663 if (targetItem ==
null)
668 if (IdentifiersOrTags !=
null && ItemCount > 1)
674 if (Equip && EquipSlotType.HasValue)
676 return character.HasEquippedItem(targetItem, EquipSlotType.Value);
680 return character.HasItem(targetItem, Equip);
685 private bool CheckItem(
Item item)
687 if (!item.
HasAccess(character)) {
return false; }
688 if (ignoredItems.Contains(item)) {
return false; };
689 if (ignoredIdentifiersOrTags !=
null && item.
HasIdentifierOrTags(ignoredIdentifiersOrTags)) {
return false; }
690 if (item.
Condition < TargetCondition) {
return false; }
691 if (ItemFilter !=
null && !ItemFilter(item)) {
return false; }
692 if (RequireNonEmpty && item.
Components.Any(i => i.IsEmpty(character))) {
return false; }
705 private void ResetInternal()
707 RemoveSubObjective(ref goToObjective);
708 targetItem = originalTarget;
710 isDoneSeeking =
false;
711 currentSearchIndex = 0;
712 currItemPriority = 0;
718 if (moveToTarget !=
null)
721 DebugConsole.NewMessage($
"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
727 private void SpeakCannotFind()
729 if (!SpeakIfFails) {
return; }
730 if (!character.IsOnPlayerTeam) {
return; }
731 if (objectiveManager.CurrentOrder != objectiveManager.CurrentObjective) {
return; }
732 if (CannotFindDialogueCondition !=
null && !CannotFindDialogueCondition()) {
return; }
733 LocalizedString msg = TextManager.Get(CannotFindDialogueIdentifierOverride,
"dialogcannotfinditem");
734 if (msg.IsNullOrEmpty() || !msg.Loaded) {
return; }
735 character.Speak(msg.Value, identifier:
"dialogcannotfinditem".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
bool TakeItem(Item item, CharacterInventory targetInventory, bool equip, bool wear=false, bool dropOtherIfCannotMove=true, bool allowSwapping=false, bool storeUnequipped=false, IEnumerable< Identifier > targetTags=null)
string CannotFindDialogueIdentifierOverride
bool EvaluateCombatPriority
ImmutableHashSet< Identifier > ignoredContainerIdentifiers
AIObjectiveGetItem(Character character, Identifier identifierOrTag, AIObjectiveManager objectiveManager, bool equip=true, bool checkInventory=true, float priorityModifier=1, bool spawnItemIfNotFound=false)
bool AllowDangerousPressure
override bool AbandonWhenCannotCompleteSubObjectives
bool AllowVariants
Are variants of the specified item allowed
bool CheckPathForEachItem
InvSlotType? EquipSlotType
AIObjectiveGetItem(Character character, IEnumerable< Identifier > identifiersOrTags, AIObjectiveManager objectiveManager, bool equip=true, bool checkInventory=true, float priorityModifier=1, bool spawnItemIfNotFound=false)
ImmutableHashSet< Identifier > ignoredIdentifiersOrTags
bool AllowToFindDivingGear
AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip=true, float priorityModifier=1)
override bool AllowWhileHandcuffed
override Identifier Identifier
override void Act(float deltaTime)
Func< Item, float > GetItemPriority
override bool AllowMultipleInstances
override void OnAbandon()
static IEnumerable< Identifier > ParseIgnoredTags(IEnumerable< Identifier > identifiersOrTags)
Func< bool > CannotFindDialogueCondition
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
HashSet< Item > ignoredItems
static IEnumerable< Identifier > ParseGearTags(IEnumerable< Identifier > identifiersOrTags)
override bool CheckObjectiveState()
Should return whether the objective is completed or not.
Func< Item, bool > ItemFilter
readonly Character character
Func< AIObjective, bool > AbortCondition
Aborts the objective when this condition is true.
IndoorsSteeringManager PathSteering
HumanAIController HumanAIController
readonly AIObjectiveManager objectiveManager
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
void SelectCharacter(Character character)
bool IsItemTakenBySomeoneElse(Item item)
bool CanInteractWith(Character c, float maxDist=200.0f, bool checkVisibility=true, bool skipDistanceCheck=false)
override Vector2? SimPosition
CharacterInventory Inventory
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....
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...
Inventory ParentInventory
IEnumerable< Item > DroppedStack
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....
List< ItemComponent > Components
Entity GetRootInventoryOwner()
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)
List< Item > GetItems(bool alsoFromConnectedSubs)
bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam=false, bool allowDifferentType=false)