diff --git a/Content.Server/NPC/Components/NPCRangedCombatComponent.cs b/Content.Server/NPC/Components/NPCRangedCombatComponent.cs
index 2e4fcf5298..7ae9b47dae 100644
--- a/Content.Server/NPC/Components/NPCRangedCombatComponent.cs
+++ b/Content.Server/NPC/Components/NPCRangedCombatComponent.cs
@@ -34,6 +34,12 @@ public sealed partial class NPCRangedCombatComponent : Component
[ViewVariables(VVAccess.ReadWrite)]
public float LOSAccumulator = 0f;
+ ///
+ /// Does this predict where the target is moving towards, and then fires there?
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool Advanced = false;
+
///
/// Is the target still considered in LOS since the last check.
///
diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Ranged/GunOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Ranged/GunOperator.cs
index 53c5ed1952..177152d85a 100644
--- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Ranged/GunOperator.cs
+++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Ranged/GunOperator.cs
@@ -58,6 +58,7 @@ public override void Startup(NPCBlackboard blackboard)
base.Startup(blackboard);
var ranged = _entManager.EnsureComponent(blackboard.GetValue(NPCBlackboard.Owner));
ranged.Target = blackboard.GetValue(TargetKey);
+ ranged.Advanced = blackboard.GetValueOrDefault("AdvancedTargeting", _entManager);
if (blackboard.TryGetValue(NPCBlackboard.RotateSpeed, out var rotSpeed, _entManager))
{
diff --git a/Content.Server/NPC/NPCBlackboard.cs b/Content.Server/NPC/NPCBlackboard.cs
index 322ff0f85b..9919f2eb6a 100644
--- a/Content.Server/NPC/NPCBlackboard.cs
+++ b/Content.Server/NPC/NPCBlackboard.cs
@@ -32,6 +32,7 @@ public sealed partial class NPCBlackboard : IEnumerable _physicsQuery;
private EntityQuery _xformQuery;
- // TODO: Don't predict for hitscan
- private const float ShootSpeed = 20f;
-
///
/// Cooldown on raycasting to check LOS.
///
@@ -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)
@@ -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))
@@ -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));
}
diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs
index 7f7c7ba855..df4c83f8c9 100644
--- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs
+++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs
@@ -304,8 +304,6 @@ private void ShootOrThrow(EntityUid uid, Vector2 mapDirection, Vector2 gunVeloci
if (!HasComp(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;
}
diff --git a/Content.Server/_FTL/HeatSeeking/HeatSeekingComponent.cs b/Content.Server/_FTL/HeatSeeking/HeatSeekingComponent.cs
new file mode 100644
index 0000000000..06d7c9e14d
--- /dev/null
+++ b/Content.Server/_FTL/HeatSeeking/HeatSeekingComponent.cs
@@ -0,0 +1,41 @@
+namespace Content.Server._FTL.HeatSeeking;
+
+///
+/// This is used for...
+///
+[RegisterComponent]
+public sealed partial class HeatSeekingComponent : Component
+{
+ ///
+ /// How far does this fire a raycast onto?
+ ///
+ [DataField("seekRange")]
+ public float DefaultSeekingRange = 100f;
+
+ ///
+ /// Should this lock onto ONE entity only?
+ ///
+ [DataField]
+ public bool LockedIn;
+
+ [DataField]
+ public Angle WeaponArc = Angle.FromDegrees(360);
+
+ ///
+ /// If null it will instantly turn.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public Angle? RotationSpeed;
+
+ ///
+ /// What is this entity targeting?
+ ///
+ [DataField]
+ public EntityUid? TargetEntity;
+
+ ///
+ /// How fast does the missile accelerate?
+ ///
+ [DataField]
+ public float Acceleration = 10f;
+}
diff --git a/Content.Server/_FTL/HeatSeeking/HeatSeekingSystem.cs b/Content.Server/_FTL/HeatSeeking/HeatSeekingSystem.cs
new file mode 100644
index 0000000000..86daf9d5db
--- /dev/null
+++ b/Content.Server/_FTL/HeatSeeking/HeatSeekingSystem.cs
@@ -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;
+
+///
+/// This handles...
+///
+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();
+ 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;
+ }
+ }
+}
diff --git a/Resources/Prototypes/_FTL/Catalog/Fills/Crates/missiles.yml b/Resources/Prototypes/_FTL/Catalog/Fills/Crates/missiles.yml
index 71d42cbf69..010c1df026 100644
--- a/Resources/Prototypes/_FTL/Catalog/Fills/Crates/missiles.yml
+++ b/Resources/Prototypes/_FTL/Catalog/Fills/Crates/missiles.yml
@@ -4,7 +4,7 @@
components:
- type: StorageFill
contents:
- - id: MissileSDM
+ - id: MissileTomahawk
amount: 10
- type: entity
@@ -13,7 +13,7 @@
components:
- type: StorageFill
contents:
- - id: MissileSAM
+ - id: MissileTomahawk
amount: 10
- type: entity
@@ -22,5 +22,5 @@
components:
- type: StorageFill
contents:
- - id: MissileTAD
+ - id: MissileGR
amount: 10
diff --git a/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Ammunition/base.yml b/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Ammunition/base.yml
index 728918f93c..f68d401a57 100644
--- a/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Ammunition/base.yml
+++ b/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Ammunition/base.yml
@@ -42,7 +42,7 @@
tags:
- Missile
- type: CartridgeAmmo
- proto: BulletSDM
+ proto: BulletTomahawk
deleteOnSpawn: true
- type: StaticPrice
price: 150
diff --git a/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Ammunition/explosive.yml b/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Ammunition/explosive.yml
index 50a00c82df..29942cb32f 100644
--- a/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Ammunition/explosive.yml
+++ b/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Ammunition/explosive.yml
@@ -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
@@ -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
@@ -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
diff --git a/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Naval/Ballistic/20mm.yml b/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Naval/Ballistic/20mm.yml
index d7edd828d5..9cf81cf129 100644
--- a/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Naval/Ballistic/20mm.yml
+++ b/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Naval/Ballistic/20mm.yml
@@ -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
diff --git a/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Naval/missile.yml b/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Naval/missile.yml
index a9d2bacbb1..3958ea501c 100644
--- a/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Naval/missile.yml
+++ b/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Naval/missile.yml
@@ -3,17 +3,44 @@
id: WeaponMissileLauncher
name: I.S.T "Artemis" Missile Launcher
description: The IncSek Technologies "Artemis" Missile Launcher is a high duty missile launcher used throughout many ships and even in some CG designs.
+ # Yeah go away lore piecers, IncSek is canon - do I care? No.
placement:
mode: SnapgridCenter
components:
- - type: ItemSlots
- slots:
- gun_magazine:
- startingItem: null
- whitelist:
- tags: []
- gun_chamber:
- startingItem: MissileSDM
- whitelist:
- tags:
- - Missile
+ - type: Gun
+ projectileSpeed: 0
+ - type: ItemSlots
+ slots:
+ gun_magazine:
+ startingItem: null
+ whitelist:
+ tags: []
+ gun_chamber:
+ startingItem: MissileTomahawk
+ whitelist:
+ tags:
+ - Missile
+
+- type: entity
+ parent: WeaponMissileLauncher
+ id: WeaponMissileLauncherDebug
+ name: hand missile cannon
+ description: It's the Artemis BUT IN YOUR %%%%%% HAND!
+ suffix: DEBUG
+ placement:
+ mode: SnapgridCenter
+ components:
+ - type: Item
+ - type: Gun
+ projectileSpeed: 0
+ - type: ItemSlots
+ slots:
+ gun_magazine:
+ startingItem: null
+ whitelist:
+ tags: []
+ gun_chamber:
+ startingItem: MissileTomahawk
+ whitelist:
+ tags:
+ - Missile
diff --git a/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Projectiles/explosive.yml b/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Projectiles/explosive.yml
index 0a6d19e9c4..17bd09ed8a 100644
--- a/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Projectiles/explosive.yml
+++ b/Resources/Prototypes/_FTL/Entities/Objects/Weapons/Guns/Projectiles/explosive.yml
@@ -1,88 +1,142 @@
# missiles
- type: entity
- id: BulletSDM
- name: sdm
+ id: BaseBulletMissile
+ abstract: true
parent: BaseBulletTrigger
noSpawn: true
components:
- - type: Sprite
- sprite: _FTL/Objects/Weapons/Guns/Ammunition/Explosives/explosives.rsi
- layers:
- - state: sdm
- - type: ExplodeOnTrigger
- - type: Explosive
- explosionType: Default
- totalIntensity: 1000.0
- intensitySlope: 20 # around 50 tiles
- maxIntensity: 400
- maxTileBreak: 1
- - type: PointLight
- radius: 3.5
- color: orange
- energy: 0.5
+ - type: Sprite
+ sprite: _FTL/Objects/Weapons/Guns/Ammunition/Explosives/explosives.rsi
+ - type: ExplodeOnTrigger
+ - type: Explosive
+ explosionType: Default
+ - type: PointLight
+ radius: 3.5
+ color: orange
+ energy: 0.5
+ - type: HeatSeeking
+ rotationSpeed: 5
+ - type: NpcFactionMember
+ factions:
+ - Missile
+ - type: MobState
+ - type: Fixtures
+ fixtures:
+ projectile:
+ shape:
+ !type:PhysShapeCircle
+ radius: .5
+ mask:
+ - MobMask
+ layer:
+ - MobLayer
+ fly-by:
+ shape:
+ !type:PhysShapeCircle
+ radius: 10 # TODO: missile flyby sound
+ layer:
+ - Impassable
+ - MidImpassable
+ - HighImpassable
+ - LowImpassable
+ hard: false
+ - type: Damageable
+ damageContainer: Inorganic
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 100
+ behaviors:
+ - !type:DoActsBehavior
+ acts: [ "Destruction" ]
- type: entity
- id: BulletSAM
- name: sam
- parent: BaseBulletTrigger
+ id: BulletTomahawk
+ name: tomahawk
+ parent: BaseBulletMissile
+ noSpawn: true
+ components:
+ - type: Sprite
+ layers:
+ - state: sdm
+ - type: Explosive
+ explosionType: DemolitionCharge
+ totalIntensity: 500.0
+ intensitySlope: 50 # around 50 tiles
+ maxIntensity: 100
+ maxTileBreak: 1
+ - type: HeatSeeking
+ rotationSpeed: 10
+ acceleration: 50
+ - type: Damageable
+ damageContainer: Inorganic
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 100
+ behaviors:
+ - !type:DoActsBehavior
+ acts: [ "Destruction" ]
+
+- type: entity
+ id: BulletWolf
+ name: wolf
+ parent: BaseBulletMissile
noSpawn: true
components:
- type: Sprite
- sprite: _FTL/Objects/Weapons/Guns/Ammunition/Explosives/explosives.rsi
layers:
- state: sam
- - type: ExplodeOnTrigger
- type: Explosive
explosionType: Default
- totalIntensity: 3000.0 # large
- intensitySlope: 1
- maxIntensity: 500
+ totalIntensity: 25000.0 # large
+ intensitySlope: 500
+ maxIntensity: 100
maxTileBreak: 1
- - type: PointLight
- radius: 3.5
- color: orange
- energy: 0.5
+ - type: HeatSeeking
+ acceleration: 75
+ rotationSpeed: 15
+ seekRange: 150
- type: entity
- id: BulletTAD
- name: tad
- parent: BaseBulletTrigger
+ id: BulletGR
+ name: golden-rod
+ parent: BaseBulletMissile
noSpawn: true
components:
- type: Sprite
- sprite: _FTL/Objects/Weapons/Guns/Ammunition/Explosives/explosives.rsi
layers:
- state: sam
- - type: ExplodeOnTrigger
- type: Explosive
- explosionType: Default
+ explosionType: DemolitionCharge
totalIntensity: 300000.0 # large
- intensitySlope: 5
+ intensitySlope: 500
maxIntensity: 500
maxTileBreak: 1
- - type: PointLight
- radius: 3.5
- color: orange
- energy: 0.5
+ - type: HeatSeeking
+ acceleration: 175
+ seekRange: 1500
+ rotationSpeed: 25
- type: entity
id: BulletTND
name: tnd
- parent: BaseBulletTrigger
+ parent: BaseBulletMissile
noSpawn: true
components:
- type: Sprite
- sprite: _FTL/Objects/Weapons/Guns/Ammunition/Explosives/explosives.rsi
layers:
- state: sam
- - type: ExplodeOnTrigger
- type: Explosive
explosionType: Default
totalIntensity: 500000.0 # large
intensitySlope: 1
maxIntensity: 500
maxTileBreak: 1
- - type: PointLight
- radius: 3.5
- color: orange
- energy: 0.5
+ - type: HeatSeeking
+ acceleration: 155
+ seekRange: 5000
+ lockedIn: true
+ rotationSpeed: 40
diff --git a/Resources/Prototypes/_FTL/NPCs/root.yml b/Resources/Prototypes/_FTL/NPCs/root.yml
index db2ff6ceed..84d84e2082 100644
--- a/Resources/Prototypes/_FTL/NPCs/root.yml
+++ b/Resources/Prototypes/_FTL/NPCs/root.yml
@@ -5,3 +5,31 @@
- tasks:
- !type:HTNCompoundTask
task: IdleCompound
+
+- type: htnCompound
+ id: PointDefenseCompound
+ branches:
+ - tasks:
+ - !type:HTNPrimitiveTask
+ operator: !type:UtilityOperator
+ proto: NearbyGunTargetsInorganic
+
+ - !type:HTNPrimitiveTask
+ preconditions:
+ - !type:KeyExistsPrecondition
+ key: Target
+# - !type:TargetInRangePrecondition
+# targetKey: Target
+# # TODO: Non-scuffed
+# rangeKey: RangedRange
+ - !type:TargetInLOSPrecondition
+ targetKey: Target
+ rangeKey: RangedRange
+ operator: !type:GunOperator
+ targetKey: Target
+ requireLOS: true
+ services:
+ - !type:UtilityService
+ id: RangedService
+ proto: NearbyGunTargetsInorganic
+ key: Target
diff --git a/Resources/Prototypes/_FTL/NPCs/utility_queries.yml b/Resources/Prototypes/_FTL/NPCs/utility_queries.yml
new file mode 100644
index 0000000000..e9372a2976
--- /dev/null
+++ b/Resources/Prototypes/_FTL/NPCs/utility_queries.yml
@@ -0,0 +1,12 @@
+- type: utilityQuery
+ id: NearbyGunTargetsInorganic
+ query:
+ - !type:NearbyHostilesQuery
+ considerations:
+ - !type:TargetDistanceCon
+ curve: !type:PresetCurve
+ preset: TargetDistance
+ - !type:TargetAccessibleCon
+ curve: !type:BoolCurve
+ - !type:TargetInLOSOrCurrentCon
+ curve: !type:BoolCurve
diff --git a/Resources/Prototypes/_FTL/ai_factions.yml b/Resources/Prototypes/_FTL/ai_factions.yml
index 6472dbd6ff..e96671bcbb 100644
--- a/Resources/Prototypes/_FTL/ai_factions.yml
+++ b/Resources/Prototypes/_FTL/ai_factions.yml
@@ -1,5 +1,16 @@
+- type: npcFaction
+ id: Missile
+ hostile:
+ - PointDefenseGun # this will never be used but why not ykwim
+
+- type: npcFaction
+ id: PointDefenseGun
+ hostile:
+ - Missile
+ - NanoTrasen
+
# !!!!!!HEY HEY READ THIS HEY HEY!!!!!!
-# USE THESE FOR SHIPS ONLY I BEG
+# USE THE FOLLOWING FOR SHIPS ONLY I BEG
# THANK YOU!!!!!!!!!!
- type: npcFaction
diff --git a/Resources/migration.yml b/Resources/migration.yml
index f23461afcf..d301dd3bd6 100644
--- a/Resources/migration.yml
+++ b/Resources/migration.yml
@@ -351,7 +351,7 @@ AirlockServiceCaptainLocked: AirlockCaptainLocked
# ekrixi
ChemMasterMachineCircuitboard: null
-GenericMissile: MissileSDM
+GenericMissile: MissileTomahawk
ShieldGenerator: null
WeaponSiloFedSpawner: null
@@ -383,3 +383,7 @@ SwarmMarkTwoMissileSilo: null
HoleMarkTwoMissileSilo: null
PunchMarkTwoMissileSilo: null
MachineSleeperCryopod: CryogenicSleepUnitSpawner
+
+MissileSDM: MissileTomahawk
+MissileSAM: MissileWolf
+MissileTAD: MissileGR