using Barotrauma.Extensions; using Barotrauma.Items.Components; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.Xna.Framework; namespace Barotrauma { class AIObjectiveContainItem: AIObjective { public override Identifier Identifier { get; set; } = "contain item".ToIdentifier(); protected override bool AllowWhileHandcuffed => false; public Func GetItemPriority; public ImmutableHashSet ignoredContainerIdentifiers; public bool checkInventory = true; //if the item can't be found, spawn it in the character's inventory (used by outpost NPCs and in some cases also enemy NPCs, like pirates) private readonly bool spawnItemIfNotFound; //can either be a tag or an identifier public readonly ImmutableHashSet itemIdentifiers; public readonly ItemContainer container; private readonly Item item; public Item ItemToContain { get; private set; } public int? TargetSlot; private AIObjectiveGetItem getItemObjective; private AIObjectiveGoTo goToObjective; private readonly HashSet containedItems = new HashSet(); public bool AllowToFindDivingGear { get; set; } = true; public bool AllowDangerousPressure { get; set; } public float ConditionLevel { get; set; } = 1; public bool Equip { get; set; } public bool RemoveEmpty { get; set; } = true; public bool RemoveExisting { get; set; } /// /// Only remove existing items when the contain target can't be put in the inventory /// public bool RemoveExistingWhenNecessary { get; set; } public Func RemoveExistingPredicate { get; set; } public int? RemoveMax { get; set; } public bool MoveWholeStack { get; set; } private int _itemCount = 1; public int ItemCount { get { return _itemCount; } set { _itemCount = Math.Max(value, 1); } } public AIObjectiveContainItem(Character character, Item item, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { this.container = container; this.item = item; } public AIObjectiveContainItem(Character character, Identifier itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false) : this(character, itemIdentifier.ToEnumerable().ToImmutableHashSet(), container, objectiveManager, priorityModifier, spawnItemIfNotFound) { } public AIObjectiveContainItem(Character character, ImmutableHashSet itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false) : base(character, objectiveManager, priorityModifier) { this.itemIdentifiers = itemIdentifiers; this.spawnItemIfNotFound = spawnItemIfNotFound; this.container = container; } protected override bool CheckObjectiveSpecific() { if (IsCompleted) { return true; } if (container?.Item == null || !container.Item.HasAccess(character)) { Abandon = true; return false; } if (item != null) { return container.Inventory.Contains(item); } else { return CountItems(); } } private bool CountItems() { int containedItemCount = 0; foreach (Item it in container.Inventory.AllItems) { if (CheckItem(it) && IsInTargetSlot(it)) { containedItemCount++; } } return containedItemCount >= ItemCount; } private bool CheckItem(Item item) { return item.HasIdentifierOrTags(itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character) && container.ShouldBeContained(item, out _); } protected override void Act(float deltaTime) { if (container?.Item == null) { Abandon = true; return; } ItemToContain = item ?? character.Inventory.FindItem(it => CheckItem(it) && //ignore items already in the container, unless we're trying to place to a specific slot, and the item's not in it (it.Container != container.Item || (TargetSlot.HasValue && it.Container.OwnInventory.FindIndex(it) != TargetSlot)), recursive: true); if (ItemToContain != null) { if (!character.CanInteractWith(ItemToContain, checkLinked: false)) { Abandon = true; return; } if (character.CanInteractWith(container.Item, checkLinked: false)) { static bool CanBePut(Inventory inventory, int? targetSlot, Item itemToContain) { if (targetSlot.HasValue) { return inventory.CanBePutInSlot(itemToContain, targetSlot.Value); } else { return inventory.CanBePut(itemToContain); } } if (RemoveExisting || (RemoveExistingWhenNecessary && !CanBePut(container.Inventory, TargetSlot, ItemToContain))) { HumanAIController.UnequipContainedItems(container.Item, predicate: RemoveExistingPredicate, unequipMax: RemoveMax); } else if (RemoveEmpty) { HumanAIController.UnequipEmptyItems(container.Item); } Inventory originalInventory = ItemToContain.ParentInventory; var slots = originalInventory?.FindIndices(ItemToContain); bool TryPutItem(Inventory inventory, int? targetSlot, Item itemToContain) { if (targetSlot.HasValue) { return inventory.TryPutItem(itemToContain, targetSlot.Value, allowSwapping: false, allowCombine: false, user: character); } else { return inventory.TryPutItem(itemToContain, user: character); } } if (TryPutItem(container.Inventory, TargetSlot, ItemToContain)) { if (MoveWholeStack && slots != null) { foreach (int slot in slots) { foreach (Item item in originalInventory.GetItemsAt(slot).ToList()) { TryPutItem(container.Inventory, TargetSlot, item); } } } IsCompleted = item != null || CountItems(); } else { if (ItemToContain.ParentInventory == character.Inventory && character.IsInFriendlySub) { ItemToContain.Drop(character); } Abandon = true; } } else { TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager, getDivingGearIfNeeded: AllowToFindDivingGear) { TargetName = container.Item.Name, AbortCondition = obj => container?.Item == null || container.Item.Removed || !container.Item.HasAccess(character) || (container.Item.RootContainer?.OwnInventory?.Locked ?? false) || ItemToContain == null || ItemToContain.Removed || !ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character, SpeakIfFails = !objectiveManager.IsCurrentOrder(), endNodeFilter = n => Vector2.DistanceSquared(n.Waypoint.WorldPosition, container.Item.WorldPosition) <= MathUtils.Pow2(AIObjectiveGetItem.MaxReach) }, onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref goToObjective)); } } else { if (character.Submarine == null) { Abandon = true; } else { // No matching items in the inventory, try to get an item TryAddSubObjective(ref getItemObjective, () => new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager, equip: Equip, checkInventory: checkInventory, spawnItemIfNotFound: spawnItemIfNotFound) { GetItemPriority = GetItemPriority, ignoredContainerIdentifiers = ignoredContainerIdentifiers, ignoredItems = containedItems, AllowToFindDivingGear = AllowToFindDivingGear, AllowDangerousPressure = AllowDangerousPressure, TargetCondition = ConditionLevel, ItemFilter = (Item potentialItem) => { return (RemoveEmpty ? container.CanBeContained(potentialItem) : container.Inventory.CanBePut(potentialItem)) && container.ShouldBeContained(potentialItem, out _); }, ItemCount = ItemCount, TakeWholeStack = MoveWholeStack }, onAbandon: () => { Abandon = true; }, onCompleted: () => { if (getItemObjective?.TargetItem != null) { containedItems.Add(getItemObjective.TargetItem); } RemoveSubObjective(ref getItemObjective); }); } } } public bool IsInTargetSlot(Item item) { if (TargetSlot == null) { return true; } if (container?.Inventory is ItemInventory inventory) { return inventory.IsInSlot(item, (int)TargetSlot); } return false; } public override void Reset() { base.Reset(); getItemObjective = null; goToObjective = null; containedItems.Clear(); } } }