* Construction WiP * Construction kinda works! * Lots more construction work. * It mostly works!
172 lines
5.9 KiB
C#
172 lines
5.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using SS14.Server.AI;
|
|
using SS14.Server.GameObjects;
|
|
using SS14.Server.Interfaces.GameObjects;
|
|
using SS14.Shared.Interfaces.GameObjects;
|
|
using SS14.Shared.Interfaces.GameObjects.Components;
|
|
using SS14.Shared.Interfaces.Physics;
|
|
using SS14.Shared.Interfaces.Timing;
|
|
using SS14.Shared.IoC;
|
|
using SS14.Shared.Maths;
|
|
|
|
namespace Content.Server.AI
|
|
{
|
|
/// <summary>
|
|
/// The object stays stationary. The object will periodically scan for *any* life forms in its radius, and engage them.
|
|
/// The object will rotate itself to point at the locked entity, and if it has a weapon will shoot at the entity.
|
|
/// </summary>
|
|
[AiLogicProcessor("AimShootLife")]
|
|
class AimShootLifeProcessor : AiLogicProcessor
|
|
{
|
|
private readonly ICollisionManager _physMan;
|
|
private readonly IServerEntityManager _entMan;
|
|
private readonly IGameTiming _timeMan;
|
|
|
|
private readonly List<IEntity> _workList = new List<IEntity>();
|
|
|
|
private const float MaxAngSpeed = (float)(Math.PI / 2); // how fast our turret can rotate
|
|
private const float ScanPeriod = 1.0f; // tweak this for performance and gameplay experience
|
|
private float _lastScan;
|
|
|
|
private IEntity _curTarget;
|
|
|
|
/// <summary>
|
|
/// Creates an instance of this LogicProcessor.
|
|
/// </summary>
|
|
public AimShootLifeProcessor()
|
|
{
|
|
_physMan = IoCManager.Resolve<ICollisionManager>();
|
|
_entMan = IoCManager.Resolve<IServerEntityManager>();
|
|
_timeMan = IoCManager.Resolve<IGameTiming>();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Update(float frameTime)
|
|
{
|
|
if (SelfEntity == null)
|
|
return;
|
|
|
|
DoScanning();
|
|
DoTracking(frameTime);
|
|
}
|
|
|
|
private void DoScanning()
|
|
{
|
|
var curTime = _timeMan.CurTime.TotalSeconds;
|
|
if (curTime - _lastScan > ScanPeriod)
|
|
{
|
|
_lastScan = (float)curTime;
|
|
_curTarget = FindBestTarget();
|
|
}
|
|
}
|
|
|
|
private void DoTracking(float frameTime)
|
|
{
|
|
// not valid entity to target.
|
|
if (_curTarget == null || !_curTarget.IsValid())
|
|
{
|
|
_curTarget = null;
|
|
return;
|
|
}
|
|
|
|
// point me at the target
|
|
var tarPos = _curTarget.GetComponent<ITransformComponent>().WorldPosition;
|
|
var selfTransform = SelfEntity.GetComponent<ITransformComponent>();
|
|
var myPos = selfTransform.WorldPosition;
|
|
|
|
var curDir = selfTransform.LocalRotation.ToVec();
|
|
var tarDir = (tarPos - myPos).Normalized;
|
|
|
|
var fwdAng = Vector2.Dot(curDir, tarDir);
|
|
|
|
Vector2 newDir;
|
|
if (fwdAng < 0) // target behind turret, just rotate in a direction to get target in front
|
|
{
|
|
var curRight = new Vector2(-curDir.Y, curDir.X); // right handed coord system
|
|
var rightAngle = Vector2.Dot(curDir, new Vector2(-tarDir.Y, tarDir.X)); // right handed coord system
|
|
var rotateSign = -Math.Sign(rightAngle);
|
|
newDir = curDir + curRight * rotateSign * MaxAngSpeed * frameTime;
|
|
}
|
|
else // target in front, adjust to aim at him
|
|
{
|
|
newDir = MoveTowards(curDir, tarDir, MaxAngSpeed, frameTime);
|
|
}
|
|
|
|
selfTransform.LocalRotation = new Angle(newDir);
|
|
|
|
if (fwdAng > -0.9999)
|
|
{
|
|
// TODO: shoot gun, prob need aimbot because entity rotation lags behind moving target
|
|
}
|
|
}
|
|
|
|
private IEntity FindBestTarget()
|
|
{
|
|
// "best" target is the closest one with LOS
|
|
|
|
var ents = _entMan.GetEntitiesInRange(SelfEntity, VisionRadius);
|
|
var myTransform = SelfEntity.GetComponent<ITransformComponent>();
|
|
var maxRayLen = VisionRadius * 2.5f; // circle inscribed in square, square diagonal = 2*r*sqrt(2)
|
|
|
|
_workList.Clear();
|
|
foreach (var entity in ents)
|
|
{
|
|
// filter to "people" entities (entities with controllers)
|
|
if (!entity.HasComponent<IMoverComponent>())
|
|
continue;
|
|
|
|
// build the ray
|
|
var dir = entity.GetComponent<ITransformComponent>().WorldPosition - myTransform.WorldPosition;
|
|
var ray = new Ray(myTransform.WorldPosition, dir.Normalized);
|
|
|
|
// cast the ray
|
|
var result = _physMan.IntersectRay(ray, maxRayLen);
|
|
|
|
// add to visible list
|
|
if (result.HitEntity == entity)
|
|
_workList.Add(entity);
|
|
}
|
|
|
|
// get closest entity in list
|
|
var closestEnt = GetClosest(myTransform.WorldPosition, _workList);
|
|
|
|
// return closest
|
|
return closestEnt;
|
|
}
|
|
|
|
private static IEntity GetClosest(Vector2 origin, IEnumerable<IEntity> list)
|
|
{
|
|
IEntity closest = null;
|
|
var minDistSqrd = float.PositiveInfinity;
|
|
|
|
foreach (var ent in list)
|
|
{
|
|
var pos = ent.GetComponent<ITransformComponent>().WorldPosition;
|
|
var distSqrd = (pos - origin).LengthSquared;
|
|
|
|
if (distSqrd > minDistSqrd)
|
|
continue;
|
|
|
|
closest = ent;
|
|
minDistSqrd = distSqrd;
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
private static Vector2 MoveTowards(Vector2 current, Vector2 target, float speed, float delta)
|
|
{
|
|
var maxDeltaDist = speed * delta;
|
|
var a = target - current;
|
|
var magnitude = a.Length;
|
|
if (magnitude <= maxDeltaDist)
|
|
{
|
|
return target;
|
|
}
|
|
|
|
return current + a / magnitude * maxDeltaDist;
|
|
}
|
|
}
|
|
}
|