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).
This commit is contained in:
Joonas Rikkonen
2017-12-21 19:49:26 +02:00
parent 9ed2871ede
commit 604fc65154
17 changed files with 310 additions and 169 deletions
@@ -64,7 +64,7 @@ namespace Barotrauma
protected override Vector2 DoSteeringSeek(Vector2 target, float speed = 1) 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 //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; if (findPathTimer > 0.0f) return Vector2.Zero;
@@ -73,21 +73,21 @@ namespace Barotrauma
if (character != null && character.Submarine == null) if (character != null && character.Submarine == null)
{ {
var targetHull = Hull.FindHull(FarseerPhysics.ConvertUnits.ToDisplayUnits(target), null, false); 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; pos -= targetHull.SimPosition;
} }
} }
currentPath = pathFinder.FindPath(pos, target); currentPath = pathFinder.FindPath(pos, target);
findPathTimer = Rand.Range(1.0f,1.2f); findPathTimer = Rand.Range(1.0f, 1.2f);
return DiffToCurrentNode(); return DiffToCurrentNode();
} }
Vector2 diff = DiffToCurrentNode(); Vector2 diff = DiffToCurrentNode();
var collider = character.AnimController.Collider; 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 not in water and the waypoint is between the top and bottom of the collider, no need to move vertically
if (!character.AnimController.InWater && if (!character.AnimController.InWater &&
@@ -104,7 +104,7 @@ namespace Barotrauma
private Vector2 DiffToCurrentNode() private Vector2 DiffToCurrentNode()
{ {
if (currentPath == null || currentPath.Finished || currentPath.Unreachable) return Vector2.Zero; if (currentPath == null || currentPath.Unreachable) return Vector2.Zero;
if (currentPath.Finished) if (currentPath.Finished)
{ {
@@ -113,8 +113,8 @@ namespace Barotrauma
{ {
//todo: take multiple subs into account //todo: take multiple subs into account
pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition; pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition;
} }
return currentTarget-pos2; return currentTarget - pos2;
} }
if (canOpenDoors && !character.LockHands) CheckDoorsInPath(); if (canOpenDoors && !character.LockHands) CheckDoorsInPath();
@@ -4,21 +4,13 @@ using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
class AIObjective abstract class AIObjective
{ {
protected List<AIObjective> subObjectives; protected readonly List<AIObjective> subObjectives;
protected float priority; protected float priority;
protected readonly Character character;
protected Character character;
protected string option; protected string option;
public virtual bool IsCompleted()
{
return false;
}
public virtual bool CanBeCompleted public virtual bool CanBeCompleted
{ {
get { return true; } get { return true; }
@@ -27,15 +19,12 @@ namespace Barotrauma
public string Option public string Option
{ {
get { return option; } get { return option; }
} }
public AIObjective(Character character, string option) public AIObjective(Character character, string option)
{ {
subObjectives = new List<AIObjective>(); subObjectives = new List<AIObjective>();
this.character = character; this.character = character;
this.option = option; this.option = option;
#if DEBUG #if DEBUG
@@ -60,8 +49,6 @@ namespace Barotrauma
Act(deltaTime); Act(deltaTime);
} }
protected virtual void Act(float deltaTime) { }
public void AddSubObjective(AIObjective objective) public void AddSubObjective(AIObjective objective)
{ {
if (subObjectives.Any(o => o.IsDuplicate(objective))) return; if (subObjectives.Any(o => o.IsDuplicate(objective))) return;
@@ -69,19 +56,20 @@ namespace Barotrauma
subObjectives.Add(objective); 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) protected abstract void Act(float deltaTime);
{
#if DEBUG public abstract bool IsCompleted();
throw new NotImplementedException(); public abstract float GetPriority(AIObjectiveManager objectiveManager);
#else public abstract bool IsDuplicate(AIObjective otherObjective);
return (this.GetType() == otherObjective.GetType());
#endif
}
} }
} }
@@ -33,7 +33,6 @@ namespace Barotrauma
} }
coolDownTimer = CoolDown; coolDownTimer = CoolDown;
} }
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
@@ -41,17 +40,24 @@ namespace Barotrauma
coolDownTimer -= deltaTime; coolDownTimer -= deltaTime;
var weapon = character.Inventory.FindItem("weapon"); var weapon = character.Inventory.FindItem("weapon");
if (weapon==null) if (weapon == null)
{ {
Escape(deltaTime); Escape(deltaTime);
} }
else else
{ {
//TODO: make sure the weapon is ready to use (projectiles/batteries loaded)
if (!character.SelectedItems.Contains(weapon)) if (!character.SelectedItems.Contains(weapon))
{ {
character.Inventory.TryPutItem(weapon, 3, false, character); if (character.Inventory.TryPutItem(weapon, 3, false, character))
weapon.Equip(character); {
weapon.Equip(character);
}
else
{
return;
}
} }
character.CursorPosition = enemy.Position; character.CursorPosition = enemy.Position;
character.SetInput(InputType.Aim, false, true); character.SetInput(InputType.Aim, false, true);
@@ -99,8 +105,13 @@ namespace Barotrauma
return enemy.IsDead || coolDownTimer <= 0.0f; 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 //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) //(it doesn't make a difference whether the enemy does 200 or 600 damage, it's one hit kill anyway)
@@ -19,15 +19,6 @@ namespace Barotrauma
{ {
this.itemName = itemName; this.itemName = itemName;
this.container = container; 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() public override bool IsCompleted()
@@ -35,6 +26,16 @@ namespace Barotrauma
return isCompleted || container.Inventory.FindItem(itemName)!=null; 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) protected override void Act(float deltaTime)
{ {
if (isCompleted) return; if (isCompleted) return;
@@ -15,7 +15,7 @@ namespace Barotrauma
if (item == null) return false; if (item == null) return false;
var containedItems = item.ContainedItems; 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; return oxygenTank != null;
} }
@@ -42,7 +42,7 @@ namespace Barotrauma
if (containedItems == null) return; if (containedItems == null) return;
//check if there's an oxygen tank in the mask //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) if (oxygenTank != null)
{ {
@@ -66,15 +66,18 @@ namespace Barotrauma
if (subObjective != null) if (subObjective != null)
{ {
subObjective.TryComplete(deltaTime); 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 (character.AnimController.CurrentHull == null) return 100.0f;
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 100.0f - character.Oxygen; return 100.0f - character.Oxygen;
} }
@@ -1,4 +1,5 @@
using System; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -27,39 +28,23 @@ namespace Barotrauma
unreachable = new List<Hull>(); unreachable = new List<Hull>();
} }
public override bool IsCompleted()
{
return false;
}
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
{ {
var currentHull = character.AnimController.CurrentHull; var currentHull = character.AnimController.CurrentHull;
currenthullSafety = OverrideCurrentHullSafety == null ? currenthullSafety = OverrideCurrentHullSafety == null ?
GetHullSafety(currentHull, character) : (float)OverrideCurrentHullSafety; GetHullSafety(currentHull, character) : (float)OverrideCurrentHullSafety;
if (currentHull != null) if (NeedsDivingGear())
{ {
if (NeedsDivingGear()) if (!FindDivingGear(deltaTime)) return;
{
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 (searchHullTimer > 0.0f) if (searchHullTimer > 0.0f)
{ {
@@ -79,7 +64,7 @@ namespace Barotrauma
if (goToObjective != null) if (goToObjective != null)
{ {
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager; 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)) pathSteering.CurrentPath.Unreachable && !unreachable.Contains(goToObjective.Target))
{ {
unreachable.Add(goToObjective.Target as Hull); unreachable.Add(goToObjective.Target as Hull);
@@ -92,7 +77,7 @@ namespace Barotrauma
private bool FindDivingGear(float deltaTime) private bool FindDivingGear(float deltaTime)
{ {
if (divingGearObjective==null) if (divingGearObjective == null)
{ {
divingGearObjective = new AIObjectiveFindDivingGear(character, false); divingGearObjective = new AIObjectiveFindDivingGear(character, false);
} }
@@ -113,8 +98,9 @@ namespace Barotrauma
if (hull == character.AnimController.CurrentHull || unreachable.Contains(hull)) continue; if (hull == character.AnimController.CurrentHull || unreachable.Contains(hull)) continue;
float hullValue = GetHullSafety(hull, character); float hullValue = GetHullSafety(hull, character);
hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.X - hull.Position.X)); //slight preference over hulls that are closer
hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.Y - hull.Position.Y) * 2.0f); 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) if (bestHull == null || hullValue > bestValue)
{ {
@@ -144,13 +130,13 @@ namespace Barotrauma
return false; return false;
} }
public override float GetPriority(Character character) public override float GetPriority(AIObjectiveManager objectiveManager)
{ {
if (character.Oxygen < 80.0f) if (character.Oxygen < 80.0f)
{ {
return 150.0f - character.Oxygen; return 150.0f - character.Oxygen;
} }
if (character.AnimController.CurrentHull == null) return 5.0f; if (character.AnimController.CurrentHull == null) return 5.0f;
currenthullSafety = GetHullSafety(character.AnimController.CurrentHull, character); currenthullSafety = GetHullSafety(character.AnimController.CurrentHull, character);
priority = 100.0f - currenthullSafety; priority = 100.0f - currenthullSafety;
@@ -171,13 +157,12 @@ namespace Barotrauma
} }
} }
} }
if (NeedsDivingGear()) if (NeedsDivingGear())
{ {
if (divingGearObjective != null && !divingGearObjective.IsCompleted()) priority += 20.0f; if (divingGearObjective != null && !divingGearObjective.IsCompleted()) priority += 20.0f;
} }
return priority; return priority;
} }
@@ -185,11 +170,28 @@ namespace Barotrauma
{ {
if (hull == null) return 0.0f; if (hull == null) return 0.0f;
float safety = 100.0f;
float waterPercentage = (hull.WaterVolume / hull.Volume) * 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; float fireAmount = 0.0f;
var nearbyHulls = hull.GetConnectedHulls(3); var nearbyHulls = hull.GetConnectedHulls(3);
foreach (Hull hull2 in nearbyHulls) foreach (Hull hull2 in nearbyHulls)
{ {
foreach (FireSource fireSource in hull2.FireSources) foreach (FireSource fireSource in hull2.FireSources)
@@ -204,13 +206,9 @@ namespace Barotrauma
} }
} }
} }
safety -= fireAmount;
float safety = 100.0f - fireAmount; return MathHelper.Clamp(safety, 0.0f, 100.0f);
if (waterPercentage > 30.0f && character.OxygenAvailable <= 0.0f) safety -= waterPercentage;
if (hull.OxygenPercentage < 30.0f) safety -= (30.0f - hull.OxygenPercentage) * 5.0f;
return safety;
} }
} }
} }
@@ -7,7 +7,9 @@ namespace Barotrauma
{ {
class AIObjectiveFixLeak : AIObjective class AIObjectiveFixLeak : AIObjective
{ {
private Gap leak; private readonly Gap leak;
private AIObjectiveGoTo gotoObjective;
public Gap Leak public Gap Leak
{ {
@@ -20,15 +22,20 @@ namespace Barotrauma
this.leak = leak; 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; 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 leakSize = (leak.IsHorizontal ? leak.Rect.Height : leak.Rect.Width) * Math.Max(leak.Open, 0.1f);
float dist = Vector2.DistanceSquared(character.SimPosition, leak.SimPosition); float dist = Vector2.DistanceSquared(character.SimPosition, leak.SimPosition);
dist = Math.Max(dist/100.0f, 1.0f); dist = Math.Max(dist / 100.0f, 1.0f);
return Math.Min(leakSize/dist, 40.0f); return Math.Min(leakSize / dist, 40.0f);
} }
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective)
@@ -44,7 +51,7 @@ namespace Barotrauma
if (weldingTool == null) if (weldingTool == null)
{ {
subObjectives.Add(new AIObjectiveGetItem(character, "Welding Tool", true)); AddSubObjective(new AIObjectiveGetItem(character, "Welding Tool", true));
return; return;
} }
else else
@@ -52,25 +59,31 @@ namespace Barotrauma
var containedItems = weldingTool.ContainedItems; var containedItems = weldingTool.ContainedItems;
if (containedItems == null) return; 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) if (fuelTank == null)
{ {
AddSubObjective(new AIObjectiveContainItem(character, "Welding Fuel Tank", weldingTool.GetComponent<ItemContainer>())); AddSubObjective(new AIObjectiveContainItem(character, "Welding Fuel Tank", weldingTool.GetComponent<ItemContainer>()));
return;
} }
} }
var repairTool = weldingTool.GetComponent<RepairTool>(); var repairTool = weldingTool.GetComponent<RepairTool>();
if (repairTool == null) return; 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, "", true, leak));
AddSubObjective(new AIObjectiveOperateItem(repairTool, character, "", leak));
}
} }
private Vector2 GetStandPosition() private Vector2 GetStandPosition()
@@ -7,38 +7,80 @@ namespace Barotrauma
{ {
class AIObjectiveFixLeaks : AIObjective class AIObjectiveFixLeaks : AIObjective
{ {
const float UpdateGapListInterval = 10.0f; const float UpdateGapListInterval = 5.0f;
private float updateGapListTimer; private double lastGapUpdate;
private AIObjectiveIdle idleObjective; private AIObjectiveIdle idleObjective;
private AIObjectiveFindDivingGear findDivingGear;
private List<AIObjectiveFixLeak> objectiveList; private List<AIObjectiveFixLeak> objectiveList;
public AIObjectiveFixLeaks(Character character) public AIObjectiveFixLeaks(Character character)
: base (character, "") : base (character, "")
{ {
objectiveList = new List<AIObjectiveFixLeak>();
} }
public override bool IsCompleted() 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) protected override void Act(float deltaTime)
{ {
updateGapListTimer -= deltaTime; if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null)
if (updateGapListTimer<=0.0f)
{ {
UpdateGapList(); UpdateGapList();
lastGapUpdate = Timing.TotalTime;
updateGapListTimer = UpdateGapListInterval;
} }
if (objectiveList.Any()) 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); objectiveList[objectiveList.Count - 1].TryComplete(deltaTime);
if (!objectiveList[objectiveList.Count - 1].CanBeCompleted || if (!objectiveList[objectiveList.Count - 1].CanBeCompleted ||
@@ -56,6 +98,7 @@ namespace Barotrauma
private void UpdateGapList() private void UpdateGapList()
{ {
if (objectiveList == null) objectiveList = new List<AIObjectiveFixLeak>();
objectiveList.Clear(); objectiveList.Clear();
foreach (Gap gap in Gap.GapList) foreach (Gap gap in Gap.GapList)
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System;
namespace Barotrauma namespace Barotrauma
{ {
@@ -26,6 +27,16 @@ namespace Barotrauma
get { return canBeCompleted; } 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) public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false)
: base(character, "") : base(character, "")
{ {
@@ -18,6 +18,16 @@ namespace Barotrauma
private bool getDivingGearIfNeeded; private bool getDivingGearIfNeeded;
public override float GetPriority(AIObjectiveManager objectiveManager)
{
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
public override bool CanBeCompleted public override bool CanBeCompleted
{ {
get 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.AIController.SteeringManager.Reset();
character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left; 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; var indoorsSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
if (indoorsSteering.CurrentPath==null || indoorsSteering.CurrentPath.Unreachable) if (indoorsSteering.CurrentPath == null || indoorsSteering.CurrentPath.Unreachable)
{ {
indoorsSteering.SteeringWander(); indoorsSteering.SteeringWander();
} }
@@ -130,7 +140,7 @@ namespace Barotrauma
if (item.IsInsideTrigger(character.WorldPosition)) completed = true; 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(); if (completed) character.AIController.SteeringManager.Reset();
@@ -9,15 +9,21 @@ namespace Barotrauma
{ {
const float WallAvoidDistance = 150.0f; const float WallAvoidDistance = 150.0f;
AITarget currentTarget; private AITarget currentTarget;
private float newTargetTimer; private float newTargetTimer;
private AIObjectiveFindSafety findSafety;
public AIObjectiveIdle(Character character) : base(character, "") 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; return 1.0f;
} }
@@ -31,6 +37,14 @@ namespace Barotrauma
{ {
return; 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) if (newTargetTimer <= 0.0f)
{ {
@@ -92,17 +106,10 @@ namespace Barotrauma
return; return;
} }
} }
if (character.AnimController.InWater) character.AIController.SteeringManager.SteeringWander();
{ //reset vertical steering to prevent dropping down from platforms etc
character.AIController.SteeringManager.SteeringManual(deltaTime, new Vector2(0.0f, 0.5f)); character.AIController.SteeringManager.ResetY();
}
else
{
character.AIController.SteeringManager.SteeringWander();
//reset vertical steering to prevent dropping down from platforms etc
character.AIController.SteeringManager.ResetY();
}
return; return;
} }
@@ -11,15 +11,17 @@ namespace Barotrauma
private Character character; private Character character;
private AIObjective currentObjective; private AIObjective currentOrder;
public AIObjective CurrentOrder
{
get { return currentOrder; }
}
public AIObjective CurrentObjective public AIObjective CurrentObjective
{ {
get get;
{ private set;
if (currentObjective != null) return currentObjective;
return objectives.Any() ? objectives[0] : null;
}
} }
public AIObjectiveManager(Character character) public AIObjectiveManager(Character character)
@@ -47,8 +49,13 @@ namespace Barotrauma
public float GetCurrentPriority(Character character) public float GetCurrentPriority(Character character)
{ {
if (currentObjective != null) return OrderPriority; if (CurrentOrder != null &&
return (CurrentObjective == null) ? 0.0f : CurrentObjective.GetPriority(character); (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() public void UpdateObjectives()
@@ -59,43 +66,46 @@ namespace Barotrauma
objectives = objectives.FindAll(o => !o.IsCompleted() && o.CanBeCompleted); objectives = objectives.FindAll(o => !o.IsCompleted() && o.CanBeCompleted);
//sort objectives according to priority //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) 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; return;
} }
if (!objectives.Any()) return; if (!objectives.Any()) return;
objectives[0].TryComplete(deltaTime); objectives[0].TryComplete(deltaTime);
CurrentObjective = objectives[0];
} }
public void SetOrder(Order order, string option) public void SetOrder(Order order, string option)
{ {
if (order == null) return; if (order == null) return;
currentObjective = null; currentOrder = null;
switch (order.Name.ToLowerInvariant()) switch (order.Name.ToLowerInvariant())
{ {
case "follow": case "follow":
currentObjective = new AIObjectiveGoTo(Character.Controlled, character, true); currentOrder = new AIObjectiveGoTo(Character.Controlled, character, true);
break; break;
case "wait": case "wait":
currentObjective = new AIObjectiveGoTo(character, character, true); currentOrder = new AIObjectiveGoTo(character, character, true);
break; break;
case "fixleaks": case "fixleaks":
case "fix leaks": case "fix leaks":
currentObjective = new AIObjectiveFixLeaks(character); currentOrder = new AIObjectiveFixLeaks(character);
break; break;
default: default:
if (order.TargetItem == null) return; 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; break;
} }
@@ -1,5 +1,6 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
@@ -14,6 +15,8 @@ namespace Barotrauma
private bool canBeCompleted; private bool canBeCompleted;
private bool requireEquip;
public override bool CanBeCompleted public override bool CanBeCompleted
{ {
get get
@@ -27,11 +30,21 @@ namespace Barotrauma
get { return operateTarget; } get { return operateTarget; }
} }
public AIObjectiveOperateItem(ItemComponent item, Character character, string option, Entity operateTarget = null, bool useController = false) public override float GetPriority(AIObjectiveManager objectiveManager)
:base (character, option) {
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.component = item;
this.requireEquip = requireEquip;
this.operateTarget = operateTarget; this.operateTarget = operateTarget;
if (useController) if (useController)
@@ -72,6 +85,43 @@ namespace Barotrauma
} }
else 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<Holdable>();
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>() { 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; if (component.AIOperate(deltaTime, character, this)) isCompleted = true;
} }
} }
@@ -3,7 +3,7 @@ using System.Diagnostics;
namespace Barotrauma namespace Barotrauma
{ {
class AIObjectiveRescue : AIObjective /*class AIObjectiveRescue : AIObjective
{ {
private readonly Character targetCharacter; private readonly Character targetCharacter;
@@ -30,5 +30,5 @@ namespace Barotrauma
return targetCharacter.IsDead ? 1000.0f / distance : 10000.0f / distance; return targetCharacter.IsDead ? 1000.0f / distance : 10000.0f / distance;
} }
} }*/
} }
@@ -3,7 +3,7 @@ using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
class AIObjectiveRescueAll : AIObjective /*class AIObjectiveRescueAll : AIObjective
{ {
private List<Character> rescueTargets; private List<Character> rescueTargets;
@@ -44,5 +44,5 @@ namespace Barotrauma
AddSubObjective(new AIObjectiveRescue(character, target)); AddSubObjective(new AIObjectiveRescue(character, target));
} }
} }
} }*/
} }
@@ -103,11 +103,7 @@ namespace Barotrauma.Items.Components
{ {
if (character == null) return false; if (character == null) return false;
if (!character.IsKeyDown(InputType.Aim)) 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; float degreeOfSuccess = DegreeOfSuccess(character)/100.0f;
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess) if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess)
@@ -241,7 +237,7 @@ namespace Barotrauma.Items.Components
{ {
Gap leak = objective.OperateTarget as Gap; Gap leak = objective.OperateTarget as Gap;
if (leak == null) return true; if (leak == null) return true;
float dist = Vector2.Distance(leak.WorldPosition, item.WorldPosition); float dist = Vector2.Distance(leak.WorldPosition, item.WorldPosition);
//too far away -> consider this done and hope the AI is smart enough to move closer //too far away -> consider this done and hope the AI is smart enough to move closer
@@ -258,9 +258,9 @@ namespace Barotrauma.Items.Components
if (batteryToLoad == null) return true; 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; return false;
} }
} }