Refactor AI movement (#1222)

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
This commit is contained in:
metalgearsloth
2020-06-29 01:42:44 +10:00
committed by GitHub
parent 23cc6b1d4e
commit 24831bf8a0
8 changed files with 843 additions and 497 deletions

View File

@@ -1,142 +1,67 @@
using System.Collections.Generic;
using Content.Server.GameObjects.EntitySystems.JobQueues;
using System;
using System.IO;
using Content.Server.GameObjects.EntitySystems.AI.Steering;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Content.Server.AI.Operators.Movement
{
public sealed class MoveToEntityOperator : BaseMover
public sealed class MoveToEntityOperator : AiOperator
{
// Instance
private GridCoordinates _lastTargetPosition;
private IMapManager _mapManager;
// TODO: This and steering need to support InRangeUnobstructed now
private readonly IEntity _owner;
private EntityTargetSteeringRequest _request;
private readonly IEntity _target;
// For now we'll just get as close as we can because we're not doing LOS checks to be able to pick up at the max interaction range
public float ArrivalDistance { get; }
public float PathfindingProximity { get; }
// Input
public IEntity Target { get; }
public float DesiredRange { get; set; }
public MoveToEntityOperator(IEntity owner, IEntity target, float desiredRange = 1.5f)
public MoveToEntityOperator(IEntity owner, IEntity target, float arrivalDistance = 1.0f, float pathfindingProximity = 1.5f)
{
Setup(owner);
Target = target;
_mapManager = IoCManager.Resolve<IMapManager>();
DesiredRange = desiredRange;
_owner = owner;
_target = target;
ArrivalDistance = arrivalDistance;
PathfindingProximity = pathfindingProximity;
}
public override bool TryStartup()
{
if (!base.TryStartup())
{
return true;
}
var steering = EntitySystem.Get<AiSteeringSystem>();
_request = new EntityTargetSteeringRequest(_target, ArrivalDistance, PathfindingProximity);
steering.Register(_owner, _request);
return true;
}
public override void Shutdown(Outcome outcome)
{
base.Shutdown(outcome);
var steering = EntitySystem.Get<AiSteeringSystem>();
steering.Unregister(_owner);
}
public override Outcome Execute(float frameTime)
{
var baseOutcome = base.Execute(frameTime);
// TODO: Given this is probably the most common operator whatever speed boosts you can do here will be gucci
// Could also look at running it every other tick.
if (baseOutcome == Outcome.Failed ||
Target == null ||
Target.Deleted ||
Target.Transform.GridID != Owner.Transform.GridID)
switch (_request.Status)
{
HaveArrived();
return Outcome.Failed;
}
if (RouteJob != null)
{
if (RouteJob.Status != JobStatus.Finished)
{
case SteeringStatus.Pending:
DebugTools.Assert(EntitySystem.Get<AiSteeringSystem>().IsRegistered(_owner));
return Outcome.Continuing;
}
ReceivedRoute();
return Route.Count == 0 ? Outcome.Failed : Outcome.Continuing;
}
var targetRange = (Target.Transform.GridPosition.Position - Owner.Transform.GridPosition.Position).Length;
// If they move near us
if (targetRange <= DesiredRange)
{
HaveArrived();
return Outcome.Success;
}
// If the target's moved we may need to re-route.
// First we'll check if they're near another tile on the existing route and if so
// we can trim up until that point.
if (_lastTargetPosition != default &&
(Target.Transform.GridPosition.Position - _lastTargetPosition.Position).Length > 1.5f)
{
var success = false;
// Technically it should be Route.Count - 1 but if the route's empty it'll throw
var newRoute = new Queue<TileRef>(Route.Count);
for (var i = 0; i < Route.Count; i++)
{
var tile = Route.Dequeue();
newRoute.Enqueue(tile);
var tileGrid = _mapManager.GetGrid(tile.GridIndex).GridTileToLocal(tile.GridIndices);
// Don't use DesiredRange here or above in case it's smaller than a tile;
// when we get close we run straight at them anyway so it shooouullddd be okay...
if ((Target.Transform.GridPosition.Position - tileGrid.Position).Length < 1.5f)
{
success = true;
break;
}
}
if (success)
{
Route = newRoute;
_lastTargetPosition = Target.Transform.GridPosition;
TargetGrid = Target.Transform.GridPosition;
return Outcome.Continuing;
}
_lastTargetPosition = default;
}
// If they move too far or no route
if (_lastTargetPosition == default)
{
// If they're further we could try pathfinding from the furthest tile potentially?
_lastTargetPosition = Target.Transform.GridPosition;
TargetGrid = Target.Transform.GridPosition;
GetRoute();
return Outcome.Continuing;
}
AntiStuck(frameTime);
if (IsStuck)
{
return Outcome.Continuing;
}
if (TryMove())
{
return Outcome.Continuing;
}
// If we're really close just try bee-lining it?
if (Route.Count == 0)
{
if (targetRange < 1.9f)
{
// TODO: If they have a phat hitbox they could block us
NextGrid = TargetGrid;
return Outcome.Continuing;
}
if (targetRange > DesiredRange)
{
HaveArrived();
case SteeringStatus.NoPath:
return Outcome.Failed;
}
case SteeringStatus.Arrived:
return Outcome.Success;
case SteeringStatus.Moving:
DebugTools.Assert(EntitySystem.Get<AiSteeringSystem>().IsRegistered(_owner));
return Outcome.Continuing;
default:
throw new ArgumentOutOfRangeException();
}
var nextTile = Route.Dequeue();
NextTile();
NextGrid = _mapManager.GetGrid(nextTile.GridIndex).GridTileToLocal(nextTile.GridIndices);
return Outcome.Continuing;
}
}
}
}