using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Linq; using Barotrauma.Extensions; namespace Barotrauma { class AIObjectiveExtinguishFire : AIObjective { public override Identifier Identifier { get; set; } = "extinguish fire".ToIdentifier(); public override bool ForceRun => true; protected override bool ConcurrentObjectives => true; public override bool KeepDivingGearOn => true; protected override bool AllowInAnySub => true; protected override bool AllowWhileHandcuffed => false; private readonly Hull targetHull; private AIObjectiveGetItem getExtinguisherObjective; private AIObjectiveGoTo gotoObjective; public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { this.targetHull = targetHull; } protected override float GetPriority() { if (!IsAllowed) { HandleDisallowed(); return Priority; } bool isOrder = objectiveManager.HasOrder(); if (!isOrder && Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { // Don't go into rooms with any enemies, unless it's an order Priority = 0; Abandon = true; } else { float characterY = character.CurrentHull?.WorldPosition.Y ?? character.WorldPosition.Y; float distanceFactor = 1.0f; if (targetHull != character.CurrentHull && !HumanAIController.VisibleHulls.Contains(targetHull)) { distanceFactor = GetDistanceFactor( new Vector2(character.WorldPosition.Y, characterY), targetHull.WorldPosition, verticalDistanceMultiplier: 3, maxDistance: 5000, factorAtMaxDistance: 0.1f); } float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull); if (severity > 0.75f && !isOrder && targetHull.RoomName != null && !targetHull.RoomName.Contains("reactor", StringComparison.OrdinalIgnoreCase) && !targetHull.RoomName.Contains("engine", StringComparison.OrdinalIgnoreCase) && !targetHull.RoomName.Contains("command", StringComparison.OrdinalIgnoreCase)) { // Ignore severe fires to prevent casualities unless ordered to extinguish. Priority = 0; Abandon = true; } else { float devotion = CumulatedDevotion / 100; Priority = MathHelper.Lerp(0, AIObjectiveManager.MaxObjectivePriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1)); } } return Priority; } protected override bool CheckObjectiveSpecific() => targetHull.FireSources.None(); private float sinTime; protected override void Act(float deltaTime) { var extinguisherItem = character.Inventory.FindItemByTag("fireextinguisher".ToIdentifier()); if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem)) { TryAddSubObjective(ref getExtinguisherObjective, () => { if (character.IsOnPlayerTeam && !character.HasEquippedItem("fireextinguisher".ToIdentifier(), allowBroken: false)) { character.Speak(TextManager.Get("DialogFindExtinguisher").Value, null, 2.0f, "findextinguisher".ToIdentifier(), 30.0f); } var getItemObjective = new AIObjectiveGetItem(character, "fireextinguisher".ToIdentifier(), objectiveManager, equip: true) { AllowStealing = true, // If the item is inside an unsafe hull, decrease the priority GetItemPriority = i => HumanAIController.UnsafeHulls.Contains(i.CurrentHull) ? 0.1f : 1 }; if (objectiveManager.HasOrder()) { getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindfireextinguisher").Value, null, 0.0f, "dialogcannotfindfireextinguisher".ToIdentifier(), 10.0f); }; return getItemObjective; }); } else { var extinguisher = extinguisherItem.GetComponent(); if (extinguisher == null) { #if DEBUG DebugConsole.ThrowError($"{character.Name}: AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher"); #endif Abandon = true; return; } foreach (FireSource fs in targetHull.FireSources) { if (fs == null) { continue; } if (fs.Removed) { continue; } if (character.CurrentHull == null) { Abandon = true; break; } float xDist = Math.Abs(character.WorldPosition.X - fs.WorldPosition.X); float yDist = Math.Abs(character.CurrentHull.WorldPosition.Y - targetHull.WorldPosition.Y); float dist = xDist + yDist; bool inRange = dist < extinguisher.Range; bool isInDamageRange = fs.IsInDamageRange(character, fs.DamageRange) && character.CanSeeTarget(targetHull); bool moveCloser = !isInDamageRange && (!inRange || !character.CanSeeTarget(targetHull)); bool operateExtinguisher = !moveCloser || (dist < extinguisher.Range * 1.2f && character.CanSeeTarget(targetHull)); if (operateExtinguisher) { character.CursorPosition = fs.Position; Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition; character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, fromCharacterToFireSource.Length() / 2); if (extinguisherItem.RequireAimToUse) { character.SetInput(InputType.Aim, false, true); sinTime += deltaTime * 10; } character.SetInput(extinguisherItem.IsShootable ? InputType.Shoot : InputType.Use, false, true); extinguisher.Use(deltaTime, character); if (!targetHull.FireSources.Contains(fs)) { character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.DisplayName, FormatCapitals.Yes).Value, null, 0, "putoutfire".ToIdentifier(), 10.0f); } // Prevents running into the flames. objectiveManager.CurrentObjective.ForceWalk = true; } if (moveCloser) { if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range * 0.8f) { DialogueIdentifier = "dialogcannotreachfire".ToIdentifier(), TargetName = fs.Hull.DisplayName, }, onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref gotoObjective))) { gotoObjective.requiredCondition = () => character.CanSeeTarget(targetHull); } } else if (!operateExtinguisher || isInDamageRange) { // Don't walk into the flames. RemoveSubObjective(ref gotoObjective); SteeringManager.Reset(); } // Only target one fire source at the time. break; } } } public override void Reset() { base.Reset(); getExtinguisherObjective = null; gotoObjective = null; sinTime = 0; SteeringManager?.Reset(); } protected override void OnCompleted() { base.OnCompleted(); SteeringManager?.Reset(); } protected override void OnAbandon() { base.OnAbandon(); SteeringManager?.Reset(); } } }