From 604fc65154901dc9b957a0c165df4aab5d826ad9 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 21 Dec 2017 19:49:26 +0200 Subject: [PATCH] Human AI improvements & fixes: - Replaced item name comparisons with Prefab.NameMatches (-> item names can be changed without breaking the AIs). - Having an AIObjective set as the current order of the character doesn't automatically cause it to have a high priority. For example, the order to fix leaks has a low priority if there are no leaks to fix. - AIObjectiveFixLeaks makes sure the character is wearing a diving suit before going to fix a leak. The characters used to run in and out of flooded rooms because the AIObjectiveFindSafety objective would become active as soon as the character entered the room, causing the character to run out, and then immediately run back because they are no longer in immediate danger of drowning, making the FixLeaks objective the most high-priority one. - Characters attempt to find a room with no water in AIObjectiveIdle even if the character is wearing a diving suit. - AIObjectiveFindSafety considers flooded rooms dangerous even if the character is wearing a diving suit (-> the character attempts to go into a more dry room instead of happily idling in the flooded one). - Distance to a hull doesn't decrease its desirability nearly as much in AIObjectiveFindSafety (-> fixes characters not bothering to move into a non-flooded room if it's far away). - AIObjectiveOperateItem makes sure the item is equipped before using it (-> characters can't weld leaks with the welder in their inventory). --- .../Characters/AI/IndoorsSteeringManager.cs | 18 ++-- .../Characters/AI/Objectives/AIObjective.cs | 44 ++++------ .../AI/Objectives/AIObjectiveCombat.cs | 23 +++-- .../AI/Objectives/AIObjectiveContainItem.cs | 19 +++-- .../Objectives/AIObjectiveFindDivingGear.cs | 13 +-- .../AI/Objectives/AIObjectiveFindSafety.cs | 84 +++++++++---------- .../AI/Objectives/AIObjectiveFixLeak.cs | 37 +++++--- .../AI/Objectives/AIObjectiveFixLeaks.cs | 61 ++++++++++++-- .../AI/Objectives/AIObjectiveGetItem.cs | 11 +++ .../AI/Objectives/AIObjectiveGoTo.cs | 16 +++- .../AI/Objectives/AIObjectiveIdle.cs | 35 ++++---- .../AI/Objectives/AIObjectiveManager.cs | 42 ++++++---- .../AI/Objectives/AIObjectiveOperateItem.cs | 56 ++++++++++++- .../AI/Objectives/AIObjectiveRescue.cs | 4 +- .../AI/Objectives/AIObjectiveRescueAll.cs | 4 +- .../Items/Components/Holdable/RepairTool.cs | 8 +- .../Source/Items/Components/Turret.cs | 4 +- 17 files changed, 310 insertions(+), 169 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs index 1ba1069c6..3e0f20f84 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs @@ -64,7 +64,7 @@ namespace Barotrauma protected override Vector2 DoSteeringSeek(Vector2 target, float speed = 1) { //find a new path if one hasn't been found yet or the target is different from the current target - if (currentPath == null || Vector2.Distance(target, currentTarget)>1.0f || findPathTimer < -5.0f) + if (currentPath == null || Vector2.Distance(target, currentTarget) > 1.0f || findPathTimer < -1.0f) { if (findPathTimer > 0.0f) return Vector2.Zero; @@ -73,21 +73,21 @@ namespace Barotrauma if (character != null && character.Submarine == null) { var targetHull = Hull.FindHull(FarseerPhysics.ConvertUnits.ToDisplayUnits(target), null, false); - if (targetHull!=null && targetHull.Submarine != null) + if (targetHull != null && targetHull.Submarine != null) { pos -= targetHull.SimPosition; } - } + } currentPath = pathFinder.FindPath(pos, target); - findPathTimer = Rand.Range(1.0f,1.2f); + findPathTimer = Rand.Range(1.0f, 1.2f); return DiffToCurrentNode(); } - + Vector2 diff = DiffToCurrentNode(); - + var collider = character.AnimController.Collider; //if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically if (!character.AnimController.InWater && @@ -104,7 +104,7 @@ namespace Barotrauma private Vector2 DiffToCurrentNode() { - if (currentPath == null || currentPath.Finished || currentPath.Unreachable) return Vector2.Zero; + if (currentPath == null || currentPath.Unreachable) return Vector2.Zero; if (currentPath.Finished) { @@ -113,8 +113,8 @@ namespace Barotrauma { //todo: take multiple subs into account pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition; - } - return currentTarget-pos2; + } + return currentTarget - pos2; } if (canOpenDoors && !character.LockHands) CheckDoorsInPath(); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs index ee6381c50..0e527227e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs @@ -4,21 +4,13 @@ using System.Linq; namespace Barotrauma { - class AIObjective + abstract class AIObjective { - protected List subObjectives; - + protected readonly List subObjectives; protected float priority; - - protected Character character; - + protected readonly Character character; protected string option; - public virtual bool IsCompleted() - { - return false; - } - public virtual bool CanBeCompleted { get { return true; } @@ -27,15 +19,12 @@ namespace Barotrauma public string Option { get { return option; } - } - + } public AIObjective(Character character, string option) { subObjectives = new List(); - this.character = character; - this.option = option; #if DEBUG @@ -60,8 +49,6 @@ namespace Barotrauma Act(deltaTime); } - protected virtual void Act(float deltaTime) { } - public void AddSubObjective(AIObjective objective) { if (subObjectives.Any(o => o.IsDuplicate(objective))) return; @@ -69,19 +56,20 @@ namespace Barotrauma subObjectives.Add(objective); } - public virtual float GetPriority(Character character) + public AIObjective GetCurrentSubObjective() { - return 0.0f; + AIObjective currentSubObjective = this; + while (currentSubObjective.subObjectives.Count > 0) + { + currentSubObjective = subObjectives[0]; + } + return currentSubObjective; } - public virtual bool IsDuplicate(AIObjective otherObjective) - { -#if DEBUG - throw new NotImplementedException(); -#else - return (this.GetType() == otherObjective.GetType()); -#endif - - } + protected abstract void Act(float deltaTime); + + public abstract bool IsCompleted(); + public abstract float GetPriority(AIObjectiveManager objectiveManager); + public abstract bool IsDuplicate(AIObjective otherObjective); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs index cbe66e791..cf84ae4f3 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -33,7 +33,6 @@ namespace Barotrauma } coolDownTimer = CoolDown; - } protected override void Act(float deltaTime) @@ -41,17 +40,24 @@ namespace Barotrauma coolDownTimer -= deltaTime; var weapon = character.Inventory.FindItem("weapon"); - - if (weapon==null) + + if (weapon == null) { Escape(deltaTime); } else { + //TODO: make sure the weapon is ready to use (projectiles/batteries loaded) if (!character.SelectedItems.Contains(weapon)) { - character.Inventory.TryPutItem(weapon, 3, false, character); - weapon.Equip(character); + if (character.Inventory.TryPutItem(weapon, 3, false, character)) + { + weapon.Equip(character); + } + else + { + return; + } } character.CursorPosition = enemy.Position; character.SetInput(InputType.Aim, false, true); @@ -99,8 +105,13 @@ namespace Barotrauma return enemy.IsDead || coolDownTimer <= 0.0f; } - public override float GetPriority(Character character) + public override float GetPriority(AIObjectiveManager objectiveManager) { + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + //clamp the strength to the health of this character //(it doesn't make a difference whether the enemy does 200 or 600 damage, it's one hit kill anyway) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs index bc92c927e..9dc4e26bc 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -19,15 +19,6 @@ namespace Barotrauma { this.itemName = itemName; this.container = container; - - //check if the container has room for more items - //canBeCompleted = false; - //foreach (Item contained in container.inventory.Items) - //{ - // if (contained != null) continue; - // canBeCompleted = true; - // break; - //} } public override bool IsCompleted() @@ -35,6 +26,16 @@ namespace Barotrauma return isCompleted || container.Inventory.FindItem(itemName)!=null; } + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + + return 1.0f; + } + protected override void Act(float deltaTime) { if (isCompleted) return; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs index 0b9928d66..132408dd3 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs @@ -15,7 +15,7 @@ namespace Barotrauma if (item == null) return false; var containedItems = item.ContainedItems; - var oxygenTank = Array.Find(containedItems, i => i.Name == "Oxygen Tank" && i.Condition > 0.0f); + var oxygenTank = Array.Find(containedItems, i => i.Prefab.NameMatches("Oxygen Tank") && i.Condition > 0.0f); return oxygenTank != null; } @@ -42,7 +42,7 @@ namespace Barotrauma if (containedItems == null) return; //check if there's an oxygen tank in the mask - var oxygenTank = Array.Find(containedItems, i => i.Name == "Oxygen Tank"); + var oxygenTank = Array.Find(containedItems, i => i.Prefab.NameMatches("Oxygen Tank")); if (oxygenTank != null) { @@ -66,15 +66,18 @@ namespace Barotrauma if (subObjective != null) { subObjective.TryComplete(deltaTime); - - //isCompleted = subObjective.IsCompleted(); } } - public override float GetPriority(Character character) + public override float GetPriority(AIObjectiveManager objectiveManager) { if (character.AnimController.CurrentHull == null) return 100.0f; + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + return 100.0f - character.Oxygen; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 56de5b8e5..391900d20 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; using System.Linq; @@ -27,39 +28,23 @@ namespace Barotrauma unreachable = new List(); } + public override bool IsCompleted() + { + return false; + } + protected override void Act(float deltaTime) { - var currentHull = character.AnimController.CurrentHull; currenthullSafety = OverrideCurrentHullSafety == null ? GetHullSafety(currentHull, character) : (float)OverrideCurrentHullSafety; - - if (currentHull != null) + + if (NeedsDivingGear()) { - if (NeedsDivingGear()) - { - if (!FindDivingGear(deltaTime)) return; - } - - if (currenthullSafety > MinSafety) - { - if (Math.Abs(currentHull.WorldPosition.X - character.WorldPosition.X) > 100.0f) - { - character.AIController.SteeringManager.SteeringSeek(currentHull.SimPosition, 0.5f); - } - else - { - - character.AIController.SteeringManager.Reset(); - } - - character.AIController.SelectTarget(null); - - goToObjective = null; - return; - } + if (!FindDivingGear(deltaTime)) return; } + if (searchHullTimer > 0.0f) { @@ -79,7 +64,7 @@ namespace Barotrauma if (goToObjective != null) { var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager; - if (pathSteering!=null && pathSteering.CurrentPath!= null && + if (pathSteering != null && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Unreachable && !unreachable.Contains(goToObjective.Target)) { unreachable.Add(goToObjective.Target as Hull); @@ -92,7 +77,7 @@ namespace Barotrauma private bool FindDivingGear(float deltaTime) { - if (divingGearObjective==null) + if (divingGearObjective == null) { divingGearObjective = new AIObjectiveFindDivingGear(character, false); } @@ -113,8 +98,9 @@ namespace Barotrauma if (hull == character.AnimController.CurrentHull || unreachable.Contains(hull)) continue; float hullValue = GetHullSafety(hull, character); - hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.X - hull.Position.X)); - hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.Y - hull.Position.Y) * 2.0f); + //slight preference over hulls that are closer + hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.X - hull.Position.X)) * 0.1f; + hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.Y - hull.Position.Y)) * 0.2f; if (bestHull == null || hullValue > bestValue) { @@ -144,13 +130,13 @@ namespace Barotrauma return false; } - public override float GetPriority(Character character) + public override float GetPriority(AIObjectiveManager objectiveManager) { if (character.Oxygen < 80.0f) { return 150.0f - character.Oxygen; } - + if (character.AnimController.CurrentHull == null) return 5.0f; currenthullSafety = GetHullSafety(character.AnimController.CurrentHull, character); priority = 100.0f - currenthullSafety; @@ -171,13 +157,12 @@ namespace Barotrauma } } } - - + if (NeedsDivingGear()) { if (divingGearObjective != null && !divingGearObjective.IsCompleted()) priority += 20.0f; } - + return priority; } @@ -185,11 +170,28 @@ namespace Barotrauma { if (hull == null) return 0.0f; + float safety = 100.0f; + float waterPercentage = (hull.WaterVolume / hull.Volume) * 100.0f; + if (hull.LethalPressure > 0.0f && character.PressureProtection <= 0.0f) + { + safety -= 100.0f; + } + else if (character.OxygenAvailable <= 0.0f) + { + safety -= waterPercentage; + } + else + { + safety -= waterPercentage * 0.1f; + } + + if (hull.OxygenPercentage < 30.0f) safety -= (30.0f - hull.OxygenPercentage) * 5.0f; + + if (safety <= 0.0f) return 0.0f; + float fireAmount = 0.0f; - var nearbyHulls = hull.GetConnectedHulls(3); - foreach (Hull hull2 in nearbyHulls) { foreach (FireSource fireSource in hull2.FireSources) @@ -204,13 +206,9 @@ namespace Barotrauma } } } + safety -= fireAmount; - float safety = 100.0f - fireAmount; - - if (waterPercentage > 30.0f && character.OxygenAvailable <= 0.0f) safety -= waterPercentage; - if (hull.OxygenPercentage < 30.0f) safety -= (30.0f - hull.OxygenPercentage) * 5.0f; - - return safety; + return MathHelper.Clamp(safety, 0.0f, 100.0f); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs index 356ed3590..f1e9bfd97 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -7,7 +7,9 @@ namespace Barotrauma { class AIObjectiveFixLeak : AIObjective { - private Gap leak; + private readonly Gap leak; + + private AIObjectiveGoTo gotoObjective; public Gap Leak { @@ -20,15 +22,20 @@ namespace Barotrauma this.leak = leak; } - public override float GetPriority(Character character) + public override bool IsCompleted() + { + return leak.Open <= 0.0f || leak.Removed; + } + + public override float GetPriority(AIObjectiveManager objectiveManager) { if (leak.Open == 0.0f) return 0.0f; float leakSize = (leak.IsHorizontal ? leak.Rect.Height : leak.Rect.Width) * Math.Max(leak.Open, 0.1f); float dist = Vector2.DistanceSquared(character.SimPosition, leak.SimPosition); - dist = Math.Max(dist/100.0f, 1.0f); - return Math.Min(leakSize/dist, 40.0f); + dist = Math.Max(dist / 100.0f, 1.0f); + return Math.Min(leakSize / dist, 40.0f); } public override bool IsDuplicate(AIObjective otherObjective) @@ -44,7 +51,7 @@ namespace Barotrauma if (weldingTool == null) { - subObjectives.Add(new AIObjectiveGetItem(character, "Welding Tool", true)); + AddSubObjective(new AIObjectiveGetItem(character, "Welding Tool", true)); return; } else @@ -52,25 +59,31 @@ namespace Barotrauma var containedItems = weldingTool.ContainedItems; if (containedItems == null) return; - var fuelTank = Array.Find(containedItems, i => i.Name == "Welding Fuel Tank" && i.Condition > 0.0f); + var fuelTank = Array.Find(containedItems, i => i.Prefab.NameMatches("Welding Fuel Tank") && i.Condition > 0.0f); if (fuelTank == null) { AddSubObjective(new AIObjectiveContainItem(character, "Welding Fuel Tank", weldingTool.GetComponent())); + return; } } var repairTool = weldingTool.GetComponent(); if (repairTool == null) return; - if (Vector2.Distance(character.WorldPosition, leak.WorldPosition) > 300.0f) + Vector2 standPosition = GetStandPosition(); + + if (Vector2.DistanceSquared(character.WorldPosition, leak.WorldPosition) > 100.0f * 100.0f) { - AddSubObjective(new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character)); + var gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(standPosition), character); + if (!gotoObjective.IsCompleted()) + { + AddSubObjective(gotoObjective); + return; + } } - else - { - AddSubObjective(new AIObjectiveOperateItem(repairTool, character, "", leak)); - } + + AddSubObjective(new AIObjectiveOperateItem(repairTool, character, "", true, leak)); } private Vector2 GetStandPosition() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs index f29971f03..2414a726c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs @@ -7,38 +7,80 @@ namespace Barotrauma { class AIObjectiveFixLeaks : AIObjective { - const float UpdateGapListInterval = 10.0f; + const float UpdateGapListInterval = 5.0f; - private float updateGapListTimer; + private double lastGapUpdate; private AIObjectiveIdle idleObjective; + private AIObjectiveFindDivingGear findDivingGear; + private List objectiveList; public AIObjectiveFixLeaks(Character character) : base (character, "") { - objectiveList = new List(); } public override bool IsCompleted() { - return false; + if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null) + { + UpdateGapList(); + lastGapUpdate = Timing.TotalTime; + } + + return objectiveList.Count == 0; + } + + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null) + { + UpdateGapList(); + lastGapUpdate = Timing.TotalTime; + } + + float priority = 0.0f; + foreach (AIObjectiveFixLeak fixObjective in objectiveList) + { + //gaps from outside to inside significantly increase the priority + if (!fixObjective.Leak.IsRoomToRoom) + { + priority = Math.Max(priority + fixObjective.Leak.Open * 100.0f, 50.0f); + } + else + { + priority += fixObjective.Leak.Open * 10.0f; + } + + if (priority >= 100.0f) break; + } + + return Math.Min(priority, 100.0f); } protected override void Act(float deltaTime) { - updateGapListTimer -= deltaTime; - - if (updateGapListTimer<=0.0f) + if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null) { UpdateGapList(); - - updateGapListTimer = UpdateGapListInterval; + lastGapUpdate = Timing.TotalTime; } if (objectiveList.Any()) { + if (!objectiveList[objectiveList.Count - 1].Leak.IsRoomToRoom) + { + if (findDivingGear == null) findDivingGear = new AIObjectiveFindDivingGear(character, true); + + if (!findDivingGear.IsCompleted() && findDivingGear.CanBeCompleted) + { + findDivingGear.TryComplete(deltaTime); + return; + } + } + objectiveList[objectiveList.Count - 1].TryComplete(deltaTime); if (!objectiveList[objectiveList.Count - 1].CanBeCompleted || @@ -56,6 +98,7 @@ namespace Barotrauma private void UpdateGapList() { + if (objectiveList == null) objectiveList = new List(); objectiveList.Clear(); foreach (Gap gap in Gap.GapList) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs index 07aec2f29..05c80fa69 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Linq; +using System; namespace Barotrauma { @@ -26,6 +27,16 @@ namespace Barotrauma get { return canBeCompleted; } } + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + + return 1.0f; + } + public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "") { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs index 3a9b2e037..cf566bd8a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -18,6 +18,16 @@ namespace Barotrauma private bool getDivingGearIfNeeded; + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + + return 1.0f; + } + public override bool CanBeCompleted { get @@ -93,7 +103,7 @@ namespace Barotrauma } } - if (Vector2.Distance(currTargetPos, character.SimPosition) < 1.0f) + if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < 0.5f * 0.5f) { character.AIController.SteeringManager.Reset(); character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left; @@ -104,7 +114,7 @@ namespace Barotrauma var indoorsSteering = character.AIController.SteeringManager as IndoorsSteeringManager; - if (indoorsSteering.CurrentPath==null || indoorsSteering.CurrentPath.Unreachable) + if (indoorsSteering.CurrentPath == null || indoorsSteering.CurrentPath.Unreachable) { indoorsSteering.SteeringWander(); } @@ -130,7 +140,7 @@ namespace Barotrauma if (item.IsInsideTrigger(character.WorldPosition)) completed = true; } - completed = completed || Vector2.Distance(target != null ? target.SimPosition : targetPos, character.SimPosition) < allowedDistance; + completed = completed || Vector2.DistanceSquared(target != null ? target.SimPosition : targetPos, character.SimPosition) < allowedDistance * allowedDistance; if (completed) character.AIController.SteeringManager.Reset(); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index 59b5b6f6a..6d0c6df9f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -9,15 +9,21 @@ namespace Barotrauma { const float WallAvoidDistance = 150.0f; - AITarget currentTarget; + private AITarget currentTarget; private float newTargetTimer; + private AIObjectiveFindSafety findSafety; + public AIObjectiveIdle(Character character) : base(character, "") { - } - public override float GetPriority(Character character) + public override bool IsCompleted() + { + return false; + } + + public override float GetPriority(AIObjectiveManager objectiveManager) { return 1.0f; } @@ -31,6 +37,14 @@ namespace Barotrauma { return; } + + if (character.AnimController.InWater) + { + //attempt to find a safer place if in water + if (findSafety == null) findSafety = new AIObjectiveFindSafety(character); + findSafety.TryComplete(deltaTime); + return; + } if (newTargetTimer <= 0.0f) { @@ -92,17 +106,10 @@ namespace Barotrauma return; } } - - if (character.AnimController.InWater) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, new Vector2(0.0f, 0.5f)); - } - else - { - character.AIController.SteeringManager.SteeringWander(); - //reset vertical steering to prevent dropping down from platforms etc - character.AIController.SteeringManager.ResetY(); - } + + character.AIController.SteeringManager.SteeringWander(); + //reset vertical steering to prevent dropping down from platforms etc + character.AIController.SteeringManager.ResetY(); return; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs index 8482e3867..468434e8c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs @@ -11,15 +11,17 @@ namespace Barotrauma private Character character; - private AIObjective currentObjective; + private AIObjective currentOrder; + public AIObjective CurrentOrder + { + get { return currentOrder; } + } + public AIObjective CurrentObjective { - get - { - if (currentObjective != null) return currentObjective; - return objectives.Any() ? objectives[0] : null; - } + get; + private set; } public AIObjectiveManager(Character character) @@ -47,8 +49,13 @@ namespace Barotrauma public float GetCurrentPriority(Character character) { - if (currentObjective != null) return OrderPriority; - return (CurrentObjective == null) ? 0.0f : CurrentObjective.GetPriority(character); + if (CurrentOrder != null && + (objectives.Count == 0 || currentOrder.GetPriority(this) > objectives[0].GetPriority(this))) + { + return CurrentOrder.GetPriority(this); + } + + return objectives.Count == 0 ? 0.0f : objectives[0].GetPriority(this); } public void UpdateObjectives() @@ -59,43 +66,46 @@ namespace Barotrauma objectives = objectives.FindAll(o => !o.IsCompleted() && o.CanBeCompleted); //sort objectives according to priority - objectives.Sort((x, y) => y.GetPriority(character).CompareTo(x.GetPriority(character))); + objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this))); } public void DoCurrentObjective(float deltaTime) { - if (currentObjective != null && (!objectives.Any() || objectives[0].GetPriority(character) < OrderPriority)) + if (currentOrder != null && (!objectives.Any() || objectives[0].GetPriority(this) < currentOrder.GetPriority(this))) { - currentObjective.TryComplete(deltaTime); + CurrentObjective = currentOrder; + currentOrder.TryComplete(deltaTime); return; } if (!objectives.Any()) return; objectives[0].TryComplete(deltaTime); + + CurrentObjective = objectives[0]; } public void SetOrder(Order order, string option) { if (order == null) return; - currentObjective = null; + currentOrder = null; switch (order.Name.ToLowerInvariant()) { case "follow": - currentObjective = new AIObjectiveGoTo(Character.Controlled, character, true); + currentOrder = new AIObjectiveGoTo(Character.Controlled, character, true); break; case "wait": - currentObjective = new AIObjectiveGoTo(character, character, true); + currentOrder = new AIObjectiveGoTo(character, character, true); break; case "fixleaks": case "fix leaks": - currentObjective = new AIObjectiveFixLeaks(character); + currentOrder = new AIObjectiveFixLeaks(character); break; default: if (order.TargetItem == null) return; - currentObjective = new AIObjectiveOperateItem(order.TargetItem, character, option, null, order.UseController); + currentOrder = new AIObjectiveOperateItem(order.TargetItem, character, option, false, null, order.UseController); break; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 9788393a3..061442e73 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -1,5 +1,6 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; +using System.Collections.Generic; using System.Linq; namespace Barotrauma @@ -14,6 +15,8 @@ namespace Barotrauma private bool canBeCompleted; + private bool requireEquip; + public override bool CanBeCompleted { get @@ -27,11 +30,21 @@ namespace Barotrauma get { return operateTarget; } } - public AIObjectiveOperateItem(ItemComponent item, Character character, string option, Entity operateTarget = null, bool useController = false) - :base (character, option) + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + + return 1.0f; + } + + public AIObjectiveOperateItem(ItemComponent item, Character character, string option, bool requireEquip, Entity operateTarget = null, bool useController = false) + : base (character, option) { this.component = item; - + this.requireEquip = requireEquip; this.operateTarget = operateTarget; if (useController) @@ -72,6 +85,43 @@ namespace Barotrauma } 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) + { + DebugConsole.ThrowError("AIObjectiveOperateItem failed - equipping item " + component.Item + " is required but the item has no Holdable component"); + return; + } + + for (int i = 0; i < CharacterInventory.limbSlots.Length; i++) + { + if (CharacterInventory.limbSlots[i] == InvSlotType.Any || + !holdable.AllowedSlots.Any(s => s.HasFlag(CharacterInventory.limbSlots[i]))) + { + continue; + } + + //equip slot already taken + if (character.Inventory.Items[i] != null) + { + //try to put the item in an Any slot, and drop it if that fails + if (!character.Inventory.Items[i].AllowedSlots.Contains(InvSlotType.Any) || + !character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List() { InvSlotType.Any })) + { + character.Inventory.Items[i].Drop(); + } + } + if (character.Inventory.TryPutItem(component.Item, i, true, character)) + { + component.Item.Equip(character); + break; + } + } + return; + } + if (component.AIOperate(deltaTime, character, this)) isCompleted = true; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs index 84f291d48..60391493f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -3,7 +3,7 @@ using System.Diagnostics; namespace Barotrauma { - class AIObjectiveRescue : AIObjective + /*class AIObjectiveRescue : AIObjective { private readonly Character targetCharacter; @@ -30,5 +30,5 @@ namespace Barotrauma return targetCharacter.IsDead ? 1000.0f / distance : 10000.0f / distance; } - } + }*/ } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs index adeb631b9..784b76859 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -3,7 +3,7 @@ using System.Linq; namespace Barotrauma { - class AIObjectiveRescueAll : AIObjective + /*class AIObjectiveRescueAll : AIObjective { private List rescueTargets; @@ -44,5 +44,5 @@ namespace Barotrauma AddSubObjective(new AIObjectiveRescue(character, target)); } } - } + }*/ } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs index b6bc208fc..74ae7f130 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs @@ -103,11 +103,7 @@ namespace Barotrauma.Items.Components { if (character == null) return false; if (!character.IsKeyDown(InputType.Aim)) return false; - - //if (DoesUseFail(Character)) return false; - - //targetPosition = targetPosition.X, -targetPosition.Y); - + float degreeOfSuccess = DegreeOfSuccess(character)/100.0f; if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess) @@ -241,7 +237,7 @@ namespace Barotrauma.Items.Components { Gap leak = objective.OperateTarget as Gap; if (leak == null) return true; - + float dist = Vector2.Distance(leak.WorldPosition, item.WorldPosition); //too far away -> consider this done and hope the AI is smart enough to move closer diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index 29d5c60e4..5af91073e 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs @@ -258,9 +258,9 @@ namespace Barotrauma.Items.Components if (batteryToLoad == null) return true; - if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed*0.4f) + if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f) { - objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "")); + objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "", false)); return false; } }