2026-08-10 14:57:50 +01:00
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
2021-01-06 14:56:39 +00:00
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using MessagePack;
|
|
|
|
|
|
using Newtonsoft.Json;
|
2021-01-06 14:56:39 +00:00
|
|
|
|
using UniRx;
|
2026-09-14 17:39:12 +02:00
|
|
|
|
using CultMath;
|
|
|
|
|
|
using static CultMath.math;
|
|
|
|
|
|
using float3 = CultMath.float3;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
[MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
|
|
|
|
|
|
public class TurretControllerData : BehaviorData
|
|
|
|
|
|
{
|
|
|
|
|
|
public override Behavior CreateInstance(EquippedItem item)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new TurretController(this, item);
|
|
|
|
|
|
}
|
|
|
|
|
|
public override Behavior CreateInstance(ConsumableItemEffect item)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new TurretController(this, item);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-21 09:37:54 +01:00
|
|
|
|
|
2021-04-18 05:13:20 +01:00
|
|
|
|
public class TurretController : Behavior, IInitializableBehavior
|
2021-01-06 14:56:39 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private TurretControllerData _data;
|
2021-01-25 05:53:36 +00:00
|
|
|
|
private List<Weapon> _weapons = new List<Weapon>();
|
2021-01-27 16:14:41 +00:00
|
|
|
|
private float _shotSpeed;
|
|
|
|
|
|
private bool _predictShots;
|
2021-01-06 14:56:39 +00:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public TurretController(TurretControllerData data, EquippedItem item) : base(data, item)
|
2021-01-06 14:56:39 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
_data = data;
|
2021-04-18 05:13:20 +01:00
|
|
|
|
}
|
2021-01-06 14:56:39 +00:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public TurretController(TurretControllerData data, ConsumableItemEffect item) : base(data, item)
|
2021-04-18 05:13:20 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
_data = data;
|
2021-01-06 14:56:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-18 05:13:20 +01:00
|
|
|
|
public void Initialize()
|
2021-01-06 14:56:39 +00:00
|
|
|
|
{
|
2021-04-18 05:13:20 +01:00
|
|
|
|
foreach (var weapon in Entity.GetBehaviors<Weapon>())
|
|
|
|
|
|
{
|
|
|
|
|
|
_weapons.Add(weapon);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var vel = weapon.Evaluate(weapon.WeaponData.Velocity);
|
2021-04-18 05:13:20 +01:00
|
|
|
|
if (vel > .1f)
|
2021-01-27 16:14:41 +00:00
|
|
|
|
{
|
2021-04-18 05:13:20 +01:00
|
|
|
|
_predictShots = true;
|
|
|
|
|
|
_shotSpeed = vel;
|
2021-01-27 16:14:41 +00:00
|
|
|
|
}
|
2021-04-18 05:13:20 +01:00
|
|
|
|
}
|
2021-01-06 14:56:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-18 05:13:20 +01:00
|
|
|
|
public override bool Execute(float dt)
|
2021-01-06 14:56:39 +00:00
|
|
|
|
{
|
2021-04-18 05:13:20 +01:00
|
|
|
|
if (Entity.Target.Value != null)
|
2021-01-06 14:56:39 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var diff = Entity.Target.Value.Position - Entity.Position;
|
2021-01-27 16:14:41 +00:00
|
|
|
|
if (_predictShots)
|
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var targetHullData = Entity.ItemManager.GetData(Entity.Target.Value.Hull) as HullData;
|
|
|
|
|
|
var targetVelocity = float3(Entity.Target.Value.Velocity.x, 0, Entity.Target.Value.Velocity.y);
|
Delete AetheriaMath in favour of CultMath
AetheriaMath predates the CultMath swap and re-implemented helpers CultMath
now owns. Callers use CultMath directly: decay (Ship, AetherDrive, Entity),
damp (VelocityConversion, FieldTester, FieldDriver), first_order_intercept
(TurretController, Combat, GuidedProjectile), quadratic_bezier (FieldDriver)
and smootherstep (Ship). Parameter order matches in every case, and those
formulas have the same float evaluation order, so behaviour is unchanged.
Galaxy's commented-out segment check names distance_to_segment.
The other overloads had no callers and are dropped without a CultMath
counterpart being needed: the double Damp overloads, FindDistanceToSegment,
GetCatmullRomPosition, GetCubicSplinePosition, Smoothstep(x), and the
Deg2Rad/Rad2Deg constants.
AetherDb census, factions and hardpoint-fit are byte-identical to the
pre-cut captures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:02:41 +02:00
|
|
|
|
var predictedPosition = first_order_intercept(
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Entity.Position, float3.zero, _shotSpeed,
|
|
|
|
|
|
Entity.Target.Value.Position, targetVelocity
|
2021-01-27 16:14:41 +00:00
|
|
|
|
);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
predictedPosition.y = Entity.Zone.GetHeight(predictedPosition.xz) + targetHullData.GridOffset;
|
|
|
|
|
|
Entity.LookDirection = normalize(predictedPosition - Entity.Position);
|
2021-01-27 16:14:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Entity.LookDirection = normalize(diff);
|
2021-01-06 14:56:39 +00:00
|
|
|
|
|
2021-01-25 05:53:36 +00:00
|
|
|
|
foreach (var x in _weapons)
|
2021-01-06 14:56:39 +00:00
|
|
|
|
{
|
Fuse batch 5: agents fire per weapon, Solve owns the burst, refusal is free, arming is the blast radius
Operator rulings 2026-09-30.
* Combat and TurretController ask FireControl.AgentFires for each weapon. A
fused weapon fires whenever its target is designated (Designated, the one
visible / in-range / locked predicate PFire already used), whatever the arc;
an agent never fires one at nothing. A direct-hit weapon keeps the
AgentMinHitProbability threshold. The group's first weapon no longer speaks
for the group, and the turret's own range test is gone.
* FireControl.Solve is the one decision about what a round does, extracted
from Fire: Direct, Burst at a reach, or Refused. Fire, Inspect and the
weapons' refusal check all read it. Inspect gains Outcome and BurstReach and
the debug HUD prints them for fused weapons.
* A refused round costs nothing. InstantWeapon asks before its ammo and energy
spend (at the trigger and at each round), ConstantWeapon folds the refusal
into the gate its power request and Execute already share, so a refused beam
draws no power, wear, heat or visibility and stops.
* The arming distance is BlastRadius, measured from the shooter's position and
blind to hull size. HullAhead's exit, ArmingDistance and
PendingShot.ArmingDistance are deleted; HullAhead survives as HullEntry for
the contact fuse. A round fired at a valid target inside the radius is pushed
out along its line but keeps its target, so a departed target cancels it.
* Step settles a shot's queue state before publishing about it, so a throwing
presentation observer costs its own notification and cannot re-commit or
re-resolve the shot on every later tick.
Tests: FireControlPerWeaponTests (new; includes a fused-beam fixture), the
arming tests in FireControlCut124Tests rewritten to the literal distance,
the hull-inclusive probe premises deleted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 18:36:59 +02:00
|
|
|
|
// The same per-weapon decision Combat.cs makes; the range test is part of it (a shot out of range
|
|
|
|
|
|
// is not designated and prices at zero).
|
|
|
|
|
|
if (FireControl.AgentFires(x, Entity, Entity.Target.Value))
|
2021-01-06 14:56:39 +00:00
|
|
|
|
{
|
2021-01-25 05:53:36 +00:00
|
|
|
|
x.Activate();
|
2021-01-06 14:56:39 +00:00
|
|
|
|
}
|
2021-01-25 05:53:36 +00:00
|
|
|
|
else if (x.Firing)
|
|
|
|
|
|
x.Deactivate();
|
2021-01-06 14:56:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2021-02-27 20:53:36 +00:00
|
|
|
|
foreach (var x in _weapons)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (x.Firing)
|
|
|
|
|
|
x.Deactivate();
|
|
|
|
|
|
}
|
2021-05-11 04:45:42 +01:00
|
|
|
|
Entity.Target.Value = Entity.VisibleEnemies.FirstOrDefault(e => e is Ship);
|
2021-01-06 14:56:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|