Files
tbd-station-14/Content.Server/NPC/Systems/NPCJukeSystem.cs
metalgearsloth c31c848afd Shooting NPCs and more (#18042)
* Add pirate shooting

* Shooting working

* Basics working

* Refactor time

* More conversion

* Update primitives

* Update yml

* weh

* Building again

* Draft

* weh

* b

* Start shutdown

* Starting to take form

* Code side done

* is it worky

* Fix prototypes

* stuff

* Shitty working

* Juke events working

* Even more cleanup

* RTX

* Fix interaction combat mode and compquery

* GetAmmoCount relays

* Fix rotation speed

* Juke fixes

* fixes

* weh

* The collision avoidance never ends

* Fixes

* Pause support

* framework

* lazy

* Fix idling

* Fix drip

* goobed

* Fix takeover shutdown bug

* Merge fixes

* shitter

* Fix carpos
2023-08-01 19:48:56 -05:00

207 lines
7.7 KiB
C#

using System.Numerics;
using Content.Server.NPC.Components;
using Content.Server.NPC.Events;
using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat;
using Content.Server.Weapons.Melee;
using Content.Shared.NPC;
using Content.Shared.Weapons.Melee;
using Robust.Shared.Collections;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.NPC.Systems;
public sealed class NPCJukeSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly MeleeWeaponSystem _melee = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
private EntityQuery<NPCMeleeCombatComponent> _npcMeleeQuery;
private EntityQuery<NPCRangedCombatComponent> _npcRangedQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
public override void Initialize()
{
base.Initialize();
_npcMeleeQuery = GetEntityQuery<NPCMeleeCombatComponent>();
_npcRangedQuery = GetEntityQuery<NPCRangedCombatComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
SubscribeLocalEvent<NPCJukeComponent, EntityUnpausedEvent>(OnJukeUnpaused);
SubscribeLocalEvent<NPCJukeComponent, NPCSteeringEvent>(OnJukeSteering);
}
private void OnJukeUnpaused(EntityUid uid, NPCJukeComponent component, ref EntityUnpausedEvent args)
{
component.NextJuke += args.PausedTime;
}
private void OnJukeSteering(EntityUid uid, NPCJukeComponent component, ref NPCSteeringEvent args)
{
if (component.JukeType == JukeType.AdjacentTile)
{
if (_npcRangedQuery.TryGetComponent(uid, out var ranged) &&
ranged.Status == CombatStatus.NotInSight)
{
component.TargetTile = null;
return;
}
if (_timing.CurTime < component.NextJuke)
{
component.TargetTile = null;
return;
}
if (!TryComp<MapGridComponent>(args.Transform.GridUid, out var grid))
{
component.TargetTile = null;
return;
}
var currentTile = grid.CoordinatesToTile(args.Transform.Coordinates);
if (component.TargetTile == null)
{
var targetTile = currentTile;
var startIndex = _random.Next(8);
_physicsQuery.TryGetComponent(uid, out var ownerPhysics);
var collisionLayer = ownerPhysics?.CollisionLayer ?? 0;
var collisionMask = ownerPhysics?.CollisionMask ?? 0;
for (var i = 0; i < 8; i++)
{
var index = (startIndex + i) % 8;
var neighbor = ((Direction) index).ToIntVec() + currentTile;
var valid = true;
// TODO: Probably make this a helper on engine maybe
var tileBounds = new Box2(neighbor, neighbor + grid.TileSize);
tileBounds = tileBounds.Enlarged(-0.1f);
foreach (var ent in _lookup.GetEntitiesIntersecting(args.Transform.GridUid.Value, tileBounds))
{
if (ent == uid ||
!_physicsQuery.TryGetComponent(ent, out var physics) ||
!physics.CanCollide ||
!physics.Hard ||
((physics.CollisionMask & collisionLayer) == 0x0 &&
(physics.CollisionLayer & collisionMask) == 0x0))
{
continue;
}
valid = false;
break;
}
if (!valid)
continue;
targetTile = neighbor;
break;
}
component.TargetTile ??= targetTile;
}
var elapsed = _timing.CurTime - component.NextJuke;
// Finished juke, reset timer.
if (elapsed.TotalSeconds > component.JukeDuration ||
currentTile == component.TargetTile)
{
component.TargetTile = null;
component.NextJuke = _timing.CurTime + TimeSpan.FromSeconds(component.JukeDuration);
return;
}
var targetCoords = grid.GridTileToWorld(component.TargetTile.Value);
var targetDir = (targetCoords.Position - args.WorldPosition);
targetDir = args.OffsetRotation.RotateVec(targetDir);
const float weight = 1f;
var norm = targetDir.Normalized();
for (var i = 0; i < SharedNPCSteeringSystem.InterestDirections; i++)
{
var result = -Vector2.Dot(norm, NPCSteeringSystem.Directions[i]) * weight;
if (result < 0f)
continue;
args.Steering.Interest[i] = MathF.Max(args.Steering.Interest[i], result);
}
args.Steering.CanSeek = false;
}
if (component.JukeType == JukeType.Away)
{
// TODO: Ranged away juking
if (_npcMeleeQuery.TryGetComponent(uid, out var melee))
{
if (!_melee.TryGetWeapon(uid, out var weaponUid, out var weapon))
return;
var cdRemaining = weapon.NextAttack - _timing.CurTime;
var attackCooldown = TimeSpan.FromSeconds(1f / _melee.GetAttackRate(weaponUid, uid, weapon));
// Might as well get in range.
if (cdRemaining < attackCooldown * 0.45f)
return;
if (!_physics.TryGetNearestPoints(uid, melee.Target, out var pointA, out var pointB))
return;
var obstacleDirection = pointB - args.WorldPosition;
// If they're moving away then pursue anyway.
// If just hit then always back up a bit.
if (cdRemaining < attackCooldown * 0.90f &&
TryComp<PhysicsComponent>(melee.Target, out var targetPhysics) &&
Vector2.Dot(targetPhysics.LinearVelocity, obstacleDirection) > 0f)
{
return;
}
if (cdRemaining < TimeSpan.FromSeconds(1f / _melee.GetAttackRate(weaponUid, uid, weapon)) * 0.45f)
return;
var idealDistance = weapon.Range * 4f;
var obstacleDistance = obstacleDirection.Length();
if (obstacleDistance > idealDistance || obstacleDistance == 0f)
{
// Don't want to get too far.
return;
}
obstacleDirection = args.OffsetRotation.RotateVec(obstacleDirection);
var norm = obstacleDirection.Normalized();
var weight = obstacleDistance <= args.Steering.Radius
? 1f
: (idealDistance - obstacleDistance) / idealDistance;
for (var i = 0; i < SharedNPCSteeringSystem.InterestDirections; i++)
{
var result = -Vector2.Dot(norm, NPCSteeringSystem.Directions[i]) * weight;
if (result < 0f)
continue;
args.Steering.Interest[i] = MathF.Max(args.Steering.Interest[i], result);
}
}
args.Steering.CanSeek = false;
}
}
}