Skip to content

Commit

Permalink
Missiles minimum viable product (ekrixi-14#313)
Browse files Browse the repository at this point in the history
* Heat seeking missiles

* end me

* Point defense is successful!

* Fix YML linter

* Heatseeking improvements

* Missile tweaks

* appease the linter

* Advanced targeting

* datafield that !
  • Loading branch information
Just-a-Unity-Dev committed Jul 10, 2024
1 parent 7c1f110 commit e41b594
Show file tree
Hide file tree
Showing 17 changed files with 387 additions and 94 deletions.
6 changes: 6 additions & 0 deletions Content.Server/NPC/Components/NPCRangedCombatComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public sealed partial class NPCRangedCombatComponent : Component
[ViewVariables(VVAccess.ReadWrite)]
public float LOSAccumulator = 0f;

/// <summary>
/// Does this predict where the target is moving towards, and then fires there?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool Advanced = false;

/// <summary>
/// Is the target still considered in LOS since the last check.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public override void Startup(NPCBlackboard blackboard)
base.Startup(blackboard);
var ranged = _entManager.EnsureComponent<NPCRangedCombatComponent>(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner));
ranged.Target = blackboard.GetValue<EntityUid>(TargetKey);
ranged.Advanced = blackboard.GetValueOrDefault<bool>("AdvancedTargeting", _entManager);

if (blackboard.TryGetValue<float>(NPCBlackboard.RotateSpeed, out var rotSpeed, _entManager))
{
Expand Down
1 change: 1 addition & 0 deletions Content.Server/NPC/NPCBlackboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
{"MovementRangeClose", 0.2f},
{"MovementRange", 1.5f},
{"RangedRange", 10f},
{"AdvancedTargeting", false},
{"RotateSpeed", float.MaxValue},
{"VisionRadius", 10f},
};
Expand Down
45 changes: 30 additions & 15 deletions Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Numerics;
using Content.Server.NPC.Components;
using Content.Shared.CombatMode;
using Content.Shared.Interaction;
Expand All @@ -20,9 +21,6 @@ public sealed partial class NPCCombatSystem
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;

// TODO: Don't predict for hitscan
private const float ShootSpeed = 20f;

/// <summary>
/// Cooldown on raycasting to check LOS.
/// </summary>
Expand Down Expand Up @@ -121,20 +119,42 @@ private void UpdateRanged(float frameTime)

comp.LOSAccumulator -= frameTime;

var worldPos = _transform.GetWorldPosition(xform);
var targetPos = _transform.GetWorldPosition(targetXform);
var (x, worldRot) = _transform.GetWorldPositionRotation(xform);
var v = gun.ProjectileSpeed; // bullet velocity
var (xt, targetRot) = _transform.GetWorldPositionRotation(targetXform);
var vt = targetBody.LinearVelocity; // target velocity

// We'll work out the projected spot of the target and shoot there instead of where they are.
var distance = (targetPos - worldPos).Length();
var oldInLos = comp.TargetInLOS;
Vector2 targetSpot;
Angle goalRotation;
var dx = xt - x; // target displacement from gun
var distance = dx.Length(); // distance to target

if (comp.Advanced)
{
var phi = (-dx).ToWorldAngle() - vt.ToWorldAngle();
var theta = Math.Asin(vt.Length() / v * Math.Sin(phi.Theta));
goalRotation = dx.ToWorldAngle() + theta;
var psi = Math.PI - phi - theta;
var intercept_dist = (float)(distance * Math.Sin(theta)/Math.Sin(psi));
targetSpot = xt + vt.Normalized() * intercept_dist;
}
else
{
// We'll work out the projected spot of the target and shoot there instead of where they are.
targetSpot = xt + vt * distance / v;
goalRotation = (targetSpot - x).ToWorldAngle();
}

// TODO: Should be doing these raycasts in parallel
// Ideally we'd have 2 steps, 1. to go over the normal details for shooting and then 2. to handle beep / rotate / shoot
var oldInLos = comp.TargetInLOS;

if (comp.LOSAccumulator < 0f)
{
comp.LOSAccumulator += UnoccludedCooldown;
// For consistency with NPC steering.
comp.TargetInLOS = _interaction.InRangeUnobstructed(uid, Transform(comp.Target).Coordinates, distance + 0.1f);
comp.TargetInLOS = _interaction.InRangeUnobstructed(comp.Owner, comp.Target, distance + 0.1f) &&
(!comp.Advanced | _interaction.InRangeUnobstructed(comp.Owner, new MapCoordinates(targetSpot, xform.MapID), distance + 0.1f));
}

if (!comp.TargetInLOS)
Expand Down Expand Up @@ -162,11 +182,6 @@ private void UpdateRanged(float frameTime)
continue;
}

var mapVelocity = targetBody.LinearVelocity;
var targetSpot = targetPos + mapVelocity * distance / ShootSpeed;

// If we have a max rotation speed then do that.
var goalRotation = (targetSpot - worldPos).ToWorldAngle();
var rotationSpeed = comp.RotationSpeed;

if (!_rotate.TryRotateTo(uid, goalRotation, frameTime, comp.AccuracyThreshold, rotationSpeed?.Theta ?? double.MaxValue, xform))
Expand All @@ -187,7 +202,7 @@ private void UpdateRanged(float frameTime)

EntityCoordinates targetCordinates;

if (_mapManager.TryFindGridAt(xform.MapID, targetPos, out var gridUid, out var mapGrid))
if (_mapManager.TryFindGridAt(xform.MapID, xt, out var gridUid, out var mapGrid))
{
targetCordinates = new EntityCoordinates(gridUid, mapGrid.WorldToLocal(targetSpot));
}
Expand Down
2 changes: 0 additions & 2 deletions Content.Server/Weapons/Ranged/Systems/GunSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,6 @@ private void ShootOrThrow(EntityUid uid, Vector2 mapDirection, Vector2 gunVeloci
if (!HasComp<ProjectileComponent>(uid))
{
RemoveShootable(uid);
// TODO: Someone can probably yeet this a billion miles so need to pre-validate input somewhere up the call stack.
ThrowingSystem.TryThrow(uid, mapDirection, gun.ProjectileSpeedModified, user);
return;
}

Expand Down
41 changes: 41 additions & 0 deletions Content.Server/_FTL/HeatSeeking/HeatSeekingComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Content.Server._FTL.HeatSeeking;

/// <summary>
/// This is used for...
/// </summary>
[RegisterComponent]
public sealed partial class HeatSeekingComponent : Component
{
/// <summary>
/// How far does this fire a raycast onto?
/// </summary>
[DataField("seekRange")]
public float DefaultSeekingRange = 100f;

/// <summary>
/// Should this lock onto ONE entity only?
/// </summary>
[DataField]
public bool LockedIn;

[DataField]
public Angle WeaponArc = Angle.FromDegrees(360);

/// <summary>
/// If null it will instantly turn.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public Angle? RotationSpeed;

/// <summary>
/// What is this entity targeting?
/// </summary>
[DataField]
public EntityUid? TargetEntity;

/// <summary>
/// How fast does the missile accelerate?
/// </summary>
[DataField]
public float Acceleration = 10f;
}
59 changes: 59 additions & 0 deletions Content.Server/_FTL/HeatSeeking/HeatSeekingSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Linq;
using System.Numerics;
using Content.Shared.Interaction;
using Content.Shared.Physics;
using Robust.Server.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.Random;

namespace Content.Server._FTL.HeatSeeking;

/// <summary>
/// This handles...
/// </summary>
public sealed class HeatSeekingSystem : EntitySystem
{
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly RotateToFaceSystem _rotate = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;

public override void Update(float frameTime)
{
base.Update(frameTime);

var query = EntityQueryEnumerator<HeatSeekingComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var comp, out var xform))
{
if (comp.TargetEntity.HasValue)
{
var entXform = Transform(comp.TargetEntity.Value);
var angle = (
_transform.ToMapCoordinates(xform.Coordinates).Position -
_transform.ToMapCoordinates(entXform.Coordinates).Position
).ToWorldAngle();

_transform.SetLocalRotationNoLerp(uid, angle, xform);

if (!_rotate.TryRotateTo(uid, angle, frameTime, comp.WeaponArc, comp.RotationSpeed?.Theta ?? double.MaxValue, xform))
{
continue;
}

_physics.ApplyForce(uid, xform.LocalRotation.RotateVec(new Vector2(0, 1)) * comp.Acceleration);
return;
}

var ray = new CollisionRay(_transform.GetMapCoordinates(uid, xform).Position,
xform.LocalRotation.ToWorldVec(),
(int) (CollisionGroup.Impassable | CollisionGroup.BulletImpassable));
var results = _physics.IntersectRay(xform.MapID, ray, comp.DefaultSeekingRange, uid).ToList();
if (results.Count <= 0)
return; // nothing to heatseek ykwim

if (comp is { LockedIn: true, TargetEntity: not null })
return; // Don't reassign target entity if we have one AND we have the LockedIn property

comp.TargetEntity = results[0].HitEntity;
}
}
}
6 changes: 3 additions & 3 deletions Resources/Prototypes/_FTL/Catalog/Fills/Crates/missiles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
components:
- type: StorageFill
contents:
- id: MissileSDM
- id: MissileTomahawk
amount: 10

- type: entity
Expand All @@ -13,7 +13,7 @@
components:
- type: StorageFill
contents:
- id: MissileSAM
- id: MissileTomahawk
amount: 10

- type: entity
Expand All @@ -22,5 +22,5 @@
components:
- type: StorageFill
contents:
- id: MissileTAD
- id: MissileGR
amount: 10
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
tags:
- Missile
- type: CartridgeAmmo
proto: BulletSDM
proto: BulletTomahawk
deleteOnSpawn: true
- type: StaticPrice
price: 150
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
- type: entity
id: MissileSDM
name: SDM-VC-SR40
id: MissileTomahawk
name: tomahawk-class assault missile
suffix: Missile
parent: BaseMissile
description: A structural damage missile that requires visual contact and has a short range of 40 kilometers.
description: A basic ship assault missile. Cheap, abundant, quantity over quality - contains basic heatseeking.
components:
- type: Sprite
sprite: _FTL/Objects/Weapons/Guns/Ammunition/Explosives/explosives.rsi
state: sdm

- type: entity
id: MissileSAM
name: SAM-RC-LR40
id: MissileWolf
name: wolf-class attack missile
suffix: Missile
parent: BaseMissile
description: A ship attack missile that requires radar contact and has a long range of 4000 kilometers.
description: A standard attack missile with basic heat-seeking capabilities.
components:
- type: Sprite
sprite: _FTL/Objects/Weapons/Guns/Ammunition/Explosives/explosives.rsi
Expand All @@ -21,14 +23,15 @@
tags:
- Missile
- type: CartridgeAmmo
proto: BulletSAM
proto: BulletWolf
deleteOnSpawn: true

- type: entity
id: MissileTAD
name: TAD-FB-IR-R50
id: MissileGR
name: goldenrod-class enhanced attack missile
suffix: Missile
parent: BaseMissile
description: A total area destruction device that you should fire blindly with infinite range and has an expected blast radius of 50 meters.
description: An attack missile with enhanced payload and heat-seeking capabilities.
components:
- type: Sprite
sprite: _FTL/Objects/Weapons/Guns/Ammunition/Explosives/explosives.rsi
Expand All @@ -37,14 +40,15 @@
tags:
- Missile
- type: CartridgeAmmo
proto: BulletTAD
proto: BulletGR
deleteOnSpawn: true

- type: entity
id: MissileTND
name: TND-FB-IR-R150
name: trinity-class nuclear missile
suffix: Missile
description: A rudimentary nuclear missile. Use with caution.
parent: BaseMissile
description: A total nuclear destruction device that you should fire blindly with infinite range and has an expected blast radius of 150 meters.
components:
- type: Sprite
sprite: _FTL/Objects/Weapons/Guns/Ammunition/Explosives/explosives.rsi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,35 @@
- Cartridge20mm
- type: Gun
fireRate: 16

- type: entity
parent: BaseWeaponTurret
id: Weapon20mmPD
name: 20mm point defense gun
description: This PD gun is completely independent of any ammo systems and of any control. Make sure vicinity is clear of missiles before crossing.
components:
- type: NpcFactionMember
factions:
- PointDefenseGun
- type: BallisticAmmoProvider
proto: Cartridge20mm
capacity: 1500
- type: Gun
fireRate: 20
selectedMode: FullAuto
availableModes:
- FullAuto
- type: HTN
rootTask:
task: TurretCompound
blackboard:
RotateSpeed: !type:Single
15.705 # 3.141 * 5
SoundTargetInLOS: !type:SoundPathSpecifier
path: /Audio/Effects/double_beep.ogg
AdvancedTargeting: !type:Bool
true
RangedRange: !type:Single
60.0
VisionRadius: !type:Single
100.0
Loading

0 comments on commit e41b594

Please sign in to comment.