Human AI with pathfinding and room hazard avoidance
This commit is contained in:
@@ -16,6 +16,11 @@ namespace Barotrauma
|
||||
|
||||
protected SteeringManager steeringManager;
|
||||
|
||||
public SteeringManager SteeringManager
|
||||
{
|
||||
get { return steeringManager; }
|
||||
}
|
||||
|
||||
public Vector2 Steering
|
||||
{
|
||||
get { return Character.AnimController.TargetMovement; }
|
||||
@@ -41,8 +46,6 @@ namespace Barotrauma
|
||||
public AIController (Character c)
|
||||
{
|
||||
Character = c;
|
||||
|
||||
steeringManager = new SteeringManager(this);
|
||||
}
|
||||
|
||||
public virtual void DebugDraw(SpriteBatch spriteBatch) { }
|
||||
|
||||
@@ -80,6 +80,8 @@ namespace Barotrauma
|
||||
sight = ToolBox.GetAttributeFloat(aiElement, "sight", 0.0f);
|
||||
hearing = ToolBox.GetAttributeFloat(aiElement, "hearing", 0.0f);
|
||||
|
||||
steeringManager = new SteeringManager(this);
|
||||
|
||||
state = AiState.None;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class HumanAIController : AIController
|
||||
{
|
||||
const float UpdateObjectiveInterval = 0.5f;
|
||||
|
||||
private AIObjectiveManager objectiveManager;
|
||||
|
||||
private AITarget selectedAiTarget;
|
||||
|
||||
private float updateObjectiveTimer;
|
||||
|
||||
public HumanAIController(Character c) : base(c)
|
||||
{
|
||||
steeringManager = new PathSteeringManager(this);
|
||||
|
||||
objectiveManager = new AIObjectiveManager(c);
|
||||
objectiveManager.AddObjective(new AIObjectiveFindSafety());
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (updateObjectiveTimer>0.0f)
|
||||
{
|
||||
updateObjectiveTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.UpdateObjectives();
|
||||
updateObjectiveTimer = UpdateObjectiveInterval;
|
||||
}
|
||||
|
||||
objectiveManager.DoCurrentObjective(deltaTime);
|
||||
|
||||
//if (Character.Controlled != null)
|
||||
//{
|
||||
// steeringManager.SteeringSeek(Character.Controlled.Position);
|
||||
//}
|
||||
|
||||
Character.AnimController.IgnorePlatforms = (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
|
||||
if (Math.Abs(Character.AnimController.TargetMovement.X)>0.1f)
|
||||
{
|
||||
Character.AnimController.TargetDir = Character.AnimController.TargetMovement.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
}
|
||||
|
||||
steeringManager.Update();
|
||||
}
|
||||
|
||||
public override void SelectTarget(AITarget target)
|
||||
{
|
||||
selectedAiTarget = target;
|
||||
}
|
||||
|
||||
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
|
||||
{
|
||||
|
||||
if (selectedAiTarget != null)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, new Vector2(Character.Position.X, -Character.Position.Y), ConvertUnits.ToDisplayUnits(new Vector2(selectedAiTarget.Position.X, -selectedAiTarget.Position.Y)), Color.Red);
|
||||
}
|
||||
|
||||
PathSteeringManager pathSteering = steeringManager as PathSteeringManager;
|
||||
if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode==null) return;
|
||||
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(Character.Position.X, -Character.Position.Y),
|
||||
new Vector2(pathSteering.CurrentPath.CurrentNode.Position.X, -pathSteering.CurrentPath.CurrentNode.Position.Y),
|
||||
Color.LightGreen);
|
||||
|
||||
|
||||
for (int i = 1; i < pathSteering.CurrentPath.Nodes.Count; i++)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(pathSteering.CurrentPath.Nodes[i].Position.X, -pathSteering.CurrentPath.Nodes[i].Position.Y),
|
||||
new Vector2(pathSteering.CurrentPath.Nodes[i - 1].Position.X, -pathSteering.CurrentPath.Nodes[i-1].Position.Y),
|
||||
Color.LightGreen);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ namespace Barotrauma
|
||||
{
|
||||
protected List<AIObjective> subObjectives;
|
||||
|
||||
protected float priority;
|
||||
|
||||
public virtual bool IsCompleted()
|
||||
{
|
||||
return false;
|
||||
@@ -21,7 +23,7 @@ namespace Barotrauma
|
||||
|
||||
/// <summary>
|
||||
/// makes the character act according to the objective, or according to any subobjectives that
|
||||
/// need to be completed before this one (starting from the one with the highest priority)
|
||||
/// need to be completed before this one
|
||||
/// </summary>
|
||||
/// <param name="character">the character who's trying to achieve the objective</param>
|
||||
public void TryComplete(float deltaTime, Character character)
|
||||
@@ -38,5 +40,15 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
protected virtual void Act(float deltaTime, Character character) { }
|
||||
|
||||
public virtual float GetPriority(Character character)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public virtual bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveFindSafety : AIObjective
|
||||
{
|
||||
const float SearchHullInterval = 1.0f;
|
||||
const float MinSafety = 50.0f;
|
||||
|
||||
AIObjectiveGoTo gotoObjective;
|
||||
|
||||
float currenthullSafety;
|
||||
|
||||
float searchHullTimer;
|
||||
|
||||
protected override void Act(float deltaTime, Character character)
|
||||
{
|
||||
if (character.AnimController.CurrentHull == null || GetHullSafety(character.AnimController.CurrentHull) > MinSafety)
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringSeek(character.AnimController.CurrentHull.Position);
|
||||
|
||||
gotoObjective = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchHullTimer>0.0f)
|
||||
{
|
||||
searchHullTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
searchHullTimer = SearchHullInterval;
|
||||
|
||||
Hull bestHull = null;
|
||||
float bestValue = currenthullSafety;
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull == character.AnimController.CurrentHull) continue;
|
||||
|
||||
float hullValue = GetHullSafety(hull);
|
||||
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);
|
||||
|
||||
if (bestHull==null || hullValue > bestValue)
|
||||
{
|
||||
bestHull = hull;
|
||||
bestValue = hullValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestHull != null)
|
||||
{
|
||||
gotoObjective = new AIObjectiveGoTo(bestHull.AiTarget, character);
|
||||
//character.AIController.SelectTarget(bestHull.AiTarget);
|
||||
}
|
||||
|
||||
gotoObjective.TryComplete(deltaTime, character);
|
||||
}
|
||||
|
||||
public override float GetPriority(Character character)
|
||||
{
|
||||
if (character.AnimController.CurrentHull == null) return 0.0f;
|
||||
currenthullSafety = GetHullSafety(character.AnimController.CurrentHull);
|
||||
priority = 100.0f - currenthullSafety;
|
||||
return priority;
|
||||
}
|
||||
|
||||
private float GetHullSafety(Hull hull)
|
||||
{
|
||||
float waterPercentage = (hull.Volume / hull.FullVolume)*100.0f;
|
||||
float fireAmount = 0.0f;
|
||||
|
||||
foreach (FireSource fireSource in hull.FireSources)
|
||||
{
|
||||
fireAmount += fireSource.Size.X;
|
||||
}
|
||||
|
||||
float safety = 100.0f - fireAmount - waterPercentage;
|
||||
if (hull.OxygenPercentage < 30.0f) safety -= (30.0f-hull.OxygenPercentage)*3.0f;
|
||||
|
||||
return safety;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveGoTo : AIObjective
|
||||
{
|
||||
AITarget target;
|
||||
|
||||
private Character character;
|
||||
|
||||
public AIObjectiveGoTo(AITarget target, Character character)
|
||||
{
|
||||
this.character = character;
|
||||
this.target = target;
|
||||
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime, Character character)
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
character.AIController.SelectTarget(target);
|
||||
|
||||
character.AIController.SteeringManager.SteeringSeek(ConvertUnits.ToDisplayUnits(target.Position));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveManager
|
||||
{
|
||||
private List<AIObjective> objectives;
|
||||
|
||||
private Character character;
|
||||
|
||||
public AIObjectiveManager(Character character)
|
||||
{
|
||||
this.character = character;
|
||||
|
||||
objectives = new List<AIObjective>();
|
||||
}
|
||||
|
||||
public void AddObjective(AIObjective objective)
|
||||
{
|
||||
if (objectives.Find(o => o.IsDuplicate(objective)) != null) return;
|
||||
|
||||
objectives.Add(objective);
|
||||
}
|
||||
|
||||
public void UpdateObjectives()
|
||||
{
|
||||
if (!objectives.Any()) return;
|
||||
|
||||
//remove completed objectives
|
||||
objectives = objectives.FindAll(o => !o.IsCompleted());
|
||||
|
||||
//sort objectives according to priority
|
||||
objectives.Sort((x, y) => x.GetPriority(character).CompareTo(y.GetPriority(character)));
|
||||
|
||||
}
|
||||
|
||||
public void DoCurrentObjective(float deltaTime)
|
||||
{
|
||||
if (!objectives.Any()) return;
|
||||
objectives[0].TryComplete(deltaTime, character);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,5 +18,13 @@ namespace Barotrauma
|
||||
{
|
||||
//item.AIOperate(float deltaTime, Character character) or something
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem;
|
||||
if (operateItem == null) return false;
|
||||
|
||||
return (operateItem.targetItem == targetItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -76,6 +77,9 @@ namespace Barotrauma
|
||||
|
||||
class PathFinder
|
||||
{
|
||||
public delegate float GetNodePenaltyHandler(PathNode node, PathNode prevNode);
|
||||
public GetNodePenaltyHandler GetNodePriority;
|
||||
|
||||
List<PathNode> nodes;
|
||||
|
||||
private bool insideSubmarine;
|
||||
@@ -89,6 +93,9 @@ namespace Barotrauma
|
||||
|
||||
public SteeringPath FindPath(Vector2 start, Vector2 end)
|
||||
{
|
||||
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
float closestDist = 0.0f;
|
||||
PathNode startNode = null;
|
||||
foreach (PathNode node in nodes)
|
||||
@@ -96,11 +103,19 @@ namespace Barotrauma
|
||||
float dist = Vector2.Distance(start,node.Position);
|
||||
if (dist<closestDist || startNode==null)
|
||||
{
|
||||
if (insideSubmarine && Submarine.CheckVisibility(start, node.Position) != null) continue;
|
||||
|
||||
closestDist = dist;
|
||||
startNode = node;
|
||||
}
|
||||
}
|
||||
|
||||
if (startNode == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Pathfinding error, couldn't find a start node");
|
||||
return new SteeringPath();
|
||||
}
|
||||
|
||||
closestDist = 0.0f;
|
||||
PathNode endNode = null;
|
||||
foreach (PathNode node in nodes)
|
||||
@@ -108,18 +123,26 @@ namespace Barotrauma
|
||||
float dist = Vector2.Distance(end, node.Position);
|
||||
if (dist < closestDist || endNode == null)
|
||||
{
|
||||
if (insideSubmarine && Submarine.CheckVisibility(end, node.Waypoint.SimPosition) != null) continue;
|
||||
|
||||
closestDist = dist;
|
||||
endNode = node;
|
||||
}
|
||||
}
|
||||
|
||||
if (startNode == null || endNode == null)
|
||||
if (endNode == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Pathfinding error, couldn't find pathnodes");
|
||||
DebugConsole.ThrowError("Pathfinding error, couldn't find an end node");
|
||||
return new SteeringPath();
|
||||
}
|
||||
|
||||
return FindPath(startNode,endNode);
|
||||
|
||||
var path = FindPath(startNode,endNode);
|
||||
|
||||
sw.Stop();
|
||||
System.Diagnostics.Debug.WriteLine("findpath: " + sw.ElapsedTicks);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public SteeringPath FindPath(WayPoint start, WayPoint end)
|
||||
@@ -157,7 +180,7 @@ namespace Barotrauma
|
||||
node.G = 0.0f;
|
||||
node.H = 0.0f;
|
||||
}
|
||||
|
||||
|
||||
start.state = 1;
|
||||
while (true)
|
||||
{
|
||||
@@ -185,7 +208,10 @@ namespace Barotrauma
|
||||
//a node that hasn't been searched yet
|
||||
if (nextNode.state==0)
|
||||
{
|
||||
nextNode.H = Vector2.Distance(nextNode.Position,end.Position);
|
||||
nextNode.H = Vector2.DistanceSquared(nextNode.Position,end.Position);
|
||||
|
||||
if (GetNodePriority != null) nextNode.H += GetNodePriority(currNode, nextNode);
|
||||
|
||||
nextNode.G = currNode.G + currNode.distances[i];
|
||||
nextNode.F = nextNode.G + nextNode.H;
|
||||
nextNode.Parent = currNode;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -12,13 +13,34 @@ namespace Barotrauma
|
||||
private PathFinder pathFinder;
|
||||
private SteeringPath currentPath;
|
||||
|
||||
private Character character;
|
||||
|
||||
private List<Controller> openableButtons;
|
||||
|
||||
public SteeringPath CurrentPath
|
||||
{
|
||||
get { return currentPath; }
|
||||
}
|
||||
|
||||
public PathFinder PathFinder
|
||||
{
|
||||
get { return pathFinder; }
|
||||
}
|
||||
|
||||
private Vector2 currentTarget;
|
||||
|
||||
private float findPathTimer;
|
||||
|
||||
public PathSteeringManager(ISteerable host)
|
||||
: base(host)
|
||||
{}
|
||||
{
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true);
|
||||
pathFinder.GetNodePriority = GetNodePriority;
|
||||
|
||||
character = (host as AIController).Character;
|
||||
|
||||
openableButtons = new List<Controller>();
|
||||
}
|
||||
|
||||
public override void Update(float speed = 1)
|
||||
{
|
||||
@@ -36,14 +58,31 @@ namespace Barotrauma
|
||||
if (findPathTimer > 0.0f) return Vector2.Zero;
|
||||
|
||||
currentTarget = target;
|
||||
currentPath = pathFinder.FindPath(ConvertUnits.ToDisplayUnits(host.SimPosition), target);
|
||||
currentPath = pathFinder.FindPath(host.SimPosition, ConvertUnits.ToSimUnits(target));
|
||||
|
||||
findPathTimer = 1.0f;
|
||||
|
||||
return DiffToCurrentNode();
|
||||
}
|
||||
|
||||
|
||||
//if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode == null) return;
|
||||
|
||||
//if (currentPath.CurrentNode.ConnectedGap != null && currentPath.CurrentNode.ConnectedGap.Open < 0.9f)
|
||||
//{
|
||||
foreach (Controller controller in openableButtons)
|
||||
{
|
||||
if (Vector2.Distance(controller.Item.SimPosition, character.SimPosition) > controller.Item.PickDistance) continue;
|
||||
|
||||
controller.Item.Pick(character, false, true);
|
||||
}
|
||||
//}
|
||||
|
||||
|
||||
|
||||
Vector2 diff = DiffToCurrentNode();
|
||||
|
||||
if (diff == Vector2.Zero) return -host.Steering;
|
||||
|
||||
return (diff == Vector2.Zero) ? Vector2.Zero : Vector2.Normalize(diff)*speed;
|
||||
}
|
||||
@@ -52,11 +91,61 @@ namespace Barotrauma
|
||||
{
|
||||
if (currentPath == null) return Vector2.Zero;
|
||||
|
||||
currentPath.CheckProgress(host.SimPosition, 0.1f);
|
||||
currentPath.CheckProgress(host.SimPosition, 0.45f);
|
||||
|
||||
if (currentPath.CurrentNode == null) return Vector2.Zero;
|
||||
|
||||
return currentPath.CurrentNode.SimPosition - host.SimPosition;
|
||||
}
|
||||
|
||||
private float GetNodePriority(PathNode node, PathNode nextNode)
|
||||
{
|
||||
if (character==null) return 0.0f;
|
||||
if (nextNode.Waypoint.ConnectedGap!=null)
|
||||
{
|
||||
if (nextNode.Waypoint.ConnectedGap.Open > 0.9f) return 0.0f;
|
||||
if (nextNode.Waypoint.ConnectedGap.ConnectedDoor == null) return 100.0f;
|
||||
|
||||
var doorButtons = GetDoorButtons(nextNode.Waypoint.ConnectedGap.ConnectedDoor);
|
||||
foreach (Controller button in doorButtons)
|
||||
{
|
||||
if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) !=
|
||||
Math.Sign(node.Position.X - nextNode.Position.X)) continue;
|
||||
|
||||
if (!button.HasRequiredItems(character, false)) return 1000.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private List<Controller> GetDoorButtons(Door door)
|
||||
{
|
||||
if (door == null) return new List<Controller>();
|
||||
ConnectionPanel connectionPanel = door.Item.GetComponent<ConnectionPanel>();
|
||||
|
||||
List<Controller> doorButtons = new List<Controller>();
|
||||
|
||||
foreach (Connection c in connectionPanel.Connections)
|
||||
{
|
||||
foreach (Wire w in c.Wires)
|
||||
{
|
||||
if (w == null) continue;
|
||||
var otherConnection = w.OtherConnection(c);
|
||||
|
||||
if (otherConnection.Item == door.Item || otherConnection == null) continue;
|
||||
|
||||
var controller = otherConnection.Item.GetComponent<Controller>();
|
||||
if (controller != null)
|
||||
{
|
||||
doorButtons.Add(controller);
|
||||
if (!openableButtons.Contains(controller)) openableButtons.Add(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return doorButtons;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user