Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missiles minimum viable product #313

Merged
merged 10 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading