using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; namespace Barotrauma { class AIObjectiveOperateItem : AIObjective { public override Identifier Identifier { get; set; } = "operate item".ToIdentifier(); public override string DebugTag => $"{Identifier} {component.Name}"; public override bool AllowAutomaticItemUnequipping => true; public override bool AllowMultipleInstances => true; protected override bool AllowInAnySub => true; protected override bool AllowWhileHandcuffed => false; public override bool PrioritizeIfSubObjectivesActive => component != null && (component is Reactor || component is Turret); private readonly ItemComponent component, controller; private readonly Entity operateTarget; private readonly bool requireEquip; private readonly bool useController; private AIObjectiveGoTo goToObjective; private AIObjectiveGetItem getItemObjective; /// /// If undefined, a default filter will be used. /// public Func EndNodeFilter; public bool Override { get; init; } = true; /// /// When true, the operate objective is never completed, unless it's abandoned. /// public bool Repeat { get; init; } public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null); public override bool IsDuplicate(T otherObjective) => base.IsDuplicate(otherObjective) && otherObjective is AIObjectiveOperateItem operateObjective && operateObjective.component == component; public Entity OperateTarget => operateTarget; public ItemComponent Component => component; public ItemComponent GetTarget() => useController ? controller : component; public Func completionCondition; private bool isDoneOperating; public float? OverridePriority = null; protected override float GetPriority() { bool isOrder = objectiveManager.IsOrder(this); if (!IsAllowed) { HandleDisallowed(); return Priority; } if (!isOrder && component.Item.ConditionPercentage <= 0) { Priority = 0; } else { if (OverridePriority.HasValue) { Priority = OverridePriority.Value; } else if (isOrder) { Priority = objectiveManager.GetOrderPriority(this); } ItemComponent target = GetTarget(); Item targetItem = target?.Item; if (targetItem == null) { #if DEBUG DebugConsole.ThrowError("Item or component of AI Objective Operate item was null. This shouldn't happen."); #endif Abandon = true; Priority = 0; return Priority; } else if (targetItem.IsClaimedByBallastFlora) { Priority = 0; return Priority; } var reactor = component?.Item.GetComponent(); if (reactor != null) { if (!isOrder) { if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC) { // The reactor was previously operated by a player -> ignore. Priority = 0; return Priority; } } switch (Option.Value.ToLowerInvariant()) { case "shutdown": if (!reactor.PowerOn) { Priority = 0; return Priority; } break; case "powerup": // Check that we don't already have another order that is targeting the same item. // Without this the autonomous objective will tell the bot to turn the reactor on again. if (IsAnotherOrderTargetingSameItem(objectiveManager.ForcedOrder) || objectiveManager.CurrentOrders.Any(o => IsAnotherOrderTargetingSameItem(o.Objective))) { Priority = 0; return Priority; } bool IsAnotherOrderTargetingSameItem(AIObjective objective) { return objective is AIObjectiveOperateItem operateObjective && operateObjective != this && operateObjective.GetTarget() == target && operateObjective.Option != Option; } break; } } else if (!isOrder) { var steering = component?.Item.GetComponent(); if (steering != null && (steering.AutoPilot || HumanAIController.IsTrueForAnyCrewMember(c => c != character && c.IsCaptain, onlyActive: true, onlyConnectedSubs: true))) { // Ignore if already set to autopilot or if there's a captain onboard Priority = 0; return Priority; } } if (targetItem.CurrentHull == null || targetItem.Submarine != character.Submarine && !isOrder || targetItem.CurrentHull.FireSources.Any() || HumanAIController.IsItemOperatedByAnother(target, out _) || Character.CharacterList.Any(c => c.CurrentHull == targetItem.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)) || component.Item.IgnoreByAI(character) || useController && controller.Item.IgnoreByAI(character)) { Priority = 0; } else { if (isOrder) { float max = objectiveManager.GetOrderPriority(this); float value = CumulatedDevotion + (max * PriorityModifier); Priority = MathHelper.Clamp(value, 0, max); } else if (!OverridePriority.HasValue) { float value = CumulatedDevotion + (AIObjectiveManager.LowestOrderPriority * PriorityModifier); float max = AIObjectiveManager.LowestOrderPriority - 1; if (reactor != null && reactor.PowerOn && reactor.FissionRate > 1 && reactor.AutoTemp && Option == "powerup") { // Already on, no need to operate. value = 0; } Priority = MathHelper.Clamp(value, 0, max); } } } return Priority; } public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, Identifier option, bool requireEquip, Entity operateTarget = null, bool useController = false, ItemComponent controller = null, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier, option) { component = item ?? throw new ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target."); this.requireEquip = requireEquip; this.operateTarget = operateTarget; this.useController = useController; if (useController) { this.controller = controller ?? component?.Item?.FindController(); } var target = GetTarget(); if (target == null) { Abandon = true; #if DEBUG throw new Exception("target null"); #endif } else if (!target.Item.IsInteractable(character)) { Abandon = true; } } protected override void Act(float deltaTime) { if (character.LockHands) { Abandon = true; return; } ItemComponent target = GetTarget(); if (useController && controller == null) { if (character.IsOnPlayerTeam) { character.Speak(TextManager.GetWithVariable("DialogCantFindController", "[item]", component.Item.Name).Value, delay: 2.0f, identifier: "cantfindcontroller".ToIdentifier(), minDurationBetweenSimilar: 30.0f); } Abandon = true; return; } if (operateTarget != null) { if (HumanAIController.IsTrueForAnyBotInTheCrew(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget)) { // Another crew member is already targeting this entity (leak). Abandon = true; return; } } //the character shouldn't be grabbing anyone if it's trying to operate an item character.SelectedCharacter = null; if (target.CanBeSelected) { if (!character.IsClimbing && character.CanInteractWith(target.Item, out _, checkLinked: false)) { if (target.Item.GetComponent() is not Controller { ControlCharacterPose: true }) { HumanAIController.FaceTarget(target.Item); } else { HumanAIController.SteeringManager.Reset(); } if (character.SelectedItem != target.Item && character.SelectedSecondaryItem != target.Item) { target.Item.TryInteract(character, forceSelectKey: true); } if (component.CrewAIOperate(deltaTime, character, this)) { isDoneOperating = completionCondition == null || completionCondition(); } } else { TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager, closeEnough: 50) { TargetName = target.Item.Name, endNodeFilter = EndNodeFilter ?? AIObjectiveGetItem.CreateEndNodeFilter(target.Item) }, onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref goToObjective)); } } else { if (component.Item.GetComponent() == null) { //controller/target can't be selected and the item cannot be picked -> objective can't be completed Abandon = true; return; } else if (!character.Inventory.Contains(component.Item)) { TryAddSubObjective(ref getItemObjective, () => new AIObjectiveGetItem(character, component.Item, objectiveManager, equip: true), onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref getItemObjective)); } else { if (requireEquip && !character.HasEquippedItem(component.Item)) { //the item has to be equipped before using it if it's holdable var holdable = component.Item.GetComponent(); if (holdable == null) { #if DEBUG DebugConsole.ThrowError($"{character.Name}: AIObjectiveOperateItem failed - equipping item " + component.Item + " is required but the item has no Holdable component"); #endif return; } for (int i = 0; i < character.Inventory.Capacity; i++) { if (character.Inventory.SlotTypes[i] == InvSlotType.Any || !holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i]))) { continue; } //equip slot already taken var existingItem = character.Inventory.GetItemAt(i); if (existingItem != null) { //try to put the item in an Any slot, and drop it if that fails if (!existingItem.AllowedSlots.Contains(InvSlotType.Any) || !character.Inventory.TryPutItem(existingItem, character, new List() { InvSlotType.Any })) { existingItem.Drop(character); } } if (character.Inventory.TryPutItem(component.Item, i, true, false, character)) { component.Item.Equip(character); break; } } return; } if (component.CrewAIOperate(deltaTime, character, this)) { isDoneOperating = completionCondition == null || completionCondition(); } } } } protected override bool CheckObjectiveSpecific() => isDoneOperating && !Repeat; public override void Reset() { base.Reset(); goToObjective = null; getItemObjective = null; } } }