Human AI with pathfinding and room hazard avoidance

This commit is contained in:
Regalis
2015-11-23 01:22:38 +02:00
parent 77024a31fb
commit c456fa3c90
32 changed files with 665 additions and 126 deletions
@@ -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);
}
}
}
+32 -6
View File
@@ -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;
}
}
}