AI pickup changes (#1811)

* AI pickup changes

Eating and drinking isn't spammed anymore.
AI can do InRangeUnobstructed checks for item pickups.
AI can open drink cans.

AI littering to be coded.

* #nullable enable

* github's nullable fails are actively shortening my lifespan

* Use a const instead

So it's easier to find given the performance implications.

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
This commit is contained in:
metalgearsloth
2020-08-22 20:03:24 +10:00
committed by GitHub
parent b3156e9934
commit 72e50cce94
21 changed files with 233 additions and 41 deletions

View File

@@ -7,6 +7,7 @@ using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
using Content.Server.GameObjects.EntitySystems.JobQueues;
using Content.Server.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Server.Interfaces.Timing;
using Robust.Shared.GameObjects.Components;
@@ -42,6 +43,11 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
/// </summary>
private const float TileTolerance = 0.8f;
/// <summary>
/// How long to wait between checks (if necessary).
/// </summary>
private const float InRangeUnobstructedCooldown = 0.25f;
private Dictionary<IEntity, IAiSteeringRequest> RunningAgents => _agentLists[_listIndex];
// We'll cycle the running list every tick as all we're doing is getting a vector2 for the
@@ -208,7 +214,8 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
foreach (var (agent, steering) in RunningAgents)
{
var result = Steer(agent, steering);
// Yeah look it's not true frametime but good enough.
var result = Steer(agent, steering, frameTime * RunningAgents.Count);
steering.Status = result;
switch (result)
@@ -236,9 +243,10 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
/// </summary>
/// <param name="entity"></param>
/// <param name="steeringRequest"></param>
/// <param name="frameTime"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private SteeringStatus Steer(IEntity entity, IAiSteeringRequest steeringRequest)
private SteeringStatus Steer(IEntity entity, IAiSteeringRequest steeringRequest, float frameTime)
{
// Main optimisation to be done below is the redundant calls and adding more variables
if (!entity.TryGetComponent(out AiControllerComponent controller) || !ActionBlockerSystem.CanMove(entity))
@@ -262,13 +270,27 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
// Check if we have arrived
var targetDistance = (entity.Transform.MapPosition.Position - steeringRequest.TargetMap.Position).Length;
if (targetDistance <= steeringRequest.ArrivalDistance)
steeringRequest.TimeUntilInteractionCheck -= frameTime;
if (targetDistance <= steeringRequest.ArrivalDistance && steeringRequest.TimeUntilInteractionCheck <= 0.0f)
{
if (!steeringRequest.RequiresInRangeUnobstructed ||
InteractionChecks.InRangeUnobstructed(entity, steeringRequest.TargetMap, steeringRequest.ArrivalDistance, ignoredEnt: entity))
{
// TODO: Need cruder LOS checks for ranged weaps
controller.VelocityDir = Vector2.Zero;
return SteeringStatus.Arrived;
}
steeringRequest.TimeUntilInteractionCheck = InRangeUnobstructedCooldown;
// Welp, we'll keep on moving.
}
// If we're really close don't swiggity swoogity back and forth and just wait for the interaction check maybe?
if (steeringRequest.TimeUntilInteractionCheck > 0.0f && targetDistance <= 0.1f)
{
// TODO: If we need LOS and are moving to an entity then we may not be in range yet
// Chuck out a ray every half second or so and keep moving until we are?
// Alternatively could use tile-based LOS checks via the pathfindingsystem I guess
controller.VelocityDir = Vector2.Zero;
return SteeringStatus.Arrived;
return SteeringStatus.Moving;
}
// Handle pathfinding job

View File

@@ -10,20 +10,26 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
public GridCoordinates TargetGrid => _target.Transform.GridPosition;
public IEntity Target => _target;
private IEntity _target;
/// <inheritdoc />
public float ArrivalDistance { get; }
/// <inheritdoc />
public float PathfindingProximity { get; }
/// <summary>
/// How far the target can move before we re-path
/// How far the target can move before we re-path
/// </summary>
public float TargetMaxMove { get; } = 1.5f;
/// <summary>
/// If we need LOS on the entity first before interaction
/// </summary>
/// <inheritdoc />
public bool RequiresInRangeUnobstructed { get; }
/// <summary>
/// To avoid spamming InRangeUnobstructed we'll apply a cd to it.
/// </summary>
public float TimeUntilInteractionCheck { get; set; }
public EntityTargetSteeringRequest(IEntity target, float arrivalDistance, float pathfindingProximity = 0.5f, bool requiresInRangeUnobstructed = false)
{
_target = target;

View File

@@ -14,7 +14,12 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
/// <inheritdoc />
public float PathfindingProximity { get; }
public GridTargetSteeringRequest(GridCoordinates targetGrid, float arrivalDistance, float pathfindingProximity = 0.5f)
public bool RequiresInRangeUnobstructed { get; }
public float TimeUntilInteractionCheck { get; set; } = 0.0f;
public GridTargetSteeringRequest(GridCoordinates targetGrid, float arrivalDistance, float pathfindingProximity = 0.5f, bool requiresInRangeUnobstructed = false)
{
// Get it once up front so we the manager doesn't have to continuously get it
var mapManager = IoCManager.Resolve<IMapManager>();
@@ -22,6 +27,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
TargetGrid = targetGrid;
ArrivalDistance = arrivalDistance;
PathfindingProximity = pathfindingProximity;
RequiresInRangeUnobstructed = requiresInRangeUnobstructed;
}
}
}

View File

@@ -8,13 +8,23 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
MapCoordinates TargetMap { get; }
GridCoordinates TargetGrid { get; }
/// <summary>
/// How close we have to get before we've arrived
/// How close we have to get before we've arrived
/// </summary>
float ArrivalDistance { get; }
/// <summary>
/// How close the pathfinder needs to get. Typically you want this set lower than ArrivalDistance
/// How close the pathfinder needs to get. Typically you want this set lower than ArrivalDistance
/// </summary>
float PathfindingProximity { get; }
/// <summary>
/// If we need LOS on the entity first before interaction
/// </summary>
bool RequiresInRangeUnobstructed { get; }
/// <summary>
/// To avoid spamming InRangeUnobstructed we'll apply a cd to it.
/// </summary>
public float TimeUntilInteractionCheck { get; set; }
}
}