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
|
|
|
|
{
|
Mining Cut 3: a chunk is a target, detected by reflected light
- Entity.Target is one slot of TargetRef, read-only from outside
(IReadOnlyReactiveProperty); Entity.SetTarget is its one writer. A chunk is
taken only while the entity can see it, so a rock in darkness cannot be
picked. Every handler, agent, turret and test writes through SetTarget.
- Entity.ChunkInfo (Q14 A): the value the per-tick sensor rule settles at,
computed on demand through Sensor.PassiveRate/Sensor.Gain -- sum of the
active sensors' passive rates over (sensors x TargetInfoDecay), capped at 1.
Stored nowhere; no per-tick loop over chunks. ChunkVisible is the detection
threshold through the chunk path.
- The lost-track rule reads through the chunk path: Activate's reconciliation
and a per-tick check of the one slot drop a chunk target that has gone dark.
- Entity.VisibleChunksInReach (Q13 A): visible chunks within the longest range
among active weapons -- the one reach rule for cycling and programmatic picks.
- Entity-only readers take .Entity: TargetedBy/TargetedByCount, target items,
the stance gate (a chunk has no stance), lock (Q12 A: never on a chunk),
turrets, Combat, Minion, FireControl (a chunk fires untargeted until Cut 4).
TargetRange and the arc gate read a chunk's planar position.
- ActionGameManager: the reticle and next/previous offer visible ships and
visible chunks in reach; nearest stays enemies-only; the target panel,
indicator and fire-control debug take TargetRef. EntityInstance reads .Entity.
- MiningCut3Tests pin parity, darkness, monotone brightness, wear and break,
reach, the chunk query, target survival and loss, TargetRef equality, lock,
and field-kind assignment at generation and first load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:56:39 +02:00
|
|
|
|
// Mining Cut 3: a turret engages entities only; it never picks a chunk, and reads one as no target.
|
|
|
|
|
|
var target = Entity.Target.Value.Entity;
|
|
|
|
|
|
if (target != null)
|
2021-01-06 14:56:39 +00:00
|
|
|
|
{
|
Mining Cut 3: a chunk is a target, detected by reflected light
- Entity.Target is one slot of TargetRef, read-only from outside
(IReadOnlyReactiveProperty); Entity.SetTarget is its one writer. A chunk is
taken only while the entity can see it, so a rock in darkness cannot be
picked. Every handler, agent, turret and test writes through SetTarget.
- Entity.ChunkInfo (Q14 A): the value the per-tick sensor rule settles at,
computed on demand through Sensor.PassiveRate/Sensor.Gain -- sum of the
active sensors' passive rates over (sensors x TargetInfoDecay), capped at 1.
Stored nowhere; no per-tick loop over chunks. ChunkVisible is the detection
threshold through the chunk path.
- The lost-track rule reads through the chunk path: Activate's reconciliation
and a per-tick check of the one slot drop a chunk target that has gone dark.
- Entity.VisibleChunksInReach (Q13 A): visible chunks within the longest range
among active weapons -- the one reach rule for cycling and programmatic picks.
- Entity-only readers take .Entity: TargetedBy/TargetedByCount, target items,
the stance gate (a chunk has no stance), lock (Q12 A: never on a chunk),
turrets, Combat, Minion, FireControl (a chunk fires untargeted until Cut 4).
TargetRange and the arc gate read a chunk's planar position.
- ActionGameManager: the reticle and next/previous offer visible ships and
visible chunks in reach; nearest stays enemies-only; the target panel,
indicator and fire-control debug take TargetRef. EntityInstance reads .Entity.
- MiningCut3Tests pin parity, darkness, monotone brightness, wear and break,
reach, the chunk query, target survival and loss, TargetRef equality, lock,
and field-kind assignment at generation and first load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:56:39 +02:00
|
|
|
|
var diff = target.Position - Entity.Position;
|
2021-01-27 16:14:41 +00:00
|
|
|
|
if (_predictShots)
|
|
|
|
|
|
{
|
Mining Cut 3: a chunk is a target, detected by reflected light
- Entity.Target is one slot of TargetRef, read-only from outside
(IReadOnlyReactiveProperty); Entity.SetTarget is its one writer. A chunk is
taken only while the entity can see it, so a rock in darkness cannot be
picked. Every handler, agent, turret and test writes through SetTarget.
- Entity.ChunkInfo (Q14 A): the value the per-tick sensor rule settles at,
computed on demand through Sensor.PassiveRate/Sensor.Gain -- sum of the
active sensors' passive rates over (sensors x TargetInfoDecay), capped at 1.
Stored nowhere; no per-tick loop over chunks. ChunkVisible is the detection
threshold through the chunk path.
- The lost-track rule reads through the chunk path: Activate's reconciliation
and a per-tick check of the one slot drop a chunk target that has gone dark.
- Entity.VisibleChunksInReach (Q13 A): visible chunks within the longest range
among active weapons -- the one reach rule for cycling and programmatic picks.
- Entity-only readers take .Entity: TargetedBy/TargetedByCount, target items,
the stance gate (a chunk has no stance), lock (Q12 A: never on a chunk),
turrets, Combat, Minion, FireControl (a chunk fires untargeted until Cut 4).
TargetRange and the arc gate read a chunk's planar position.
- ActionGameManager: the reticle and next/previous offer visible ships and
visible chunks in reach; nearest stays enemies-only; the target panel,
indicator and fire-control debug take TargetRef. EntityInstance reads .Entity.
- MiningCut3Tests pin parity, darkness, monotone brightness, wear and break,
reach, the chunk query, target survival and loss, TargetRef equality, lock,
and field-kind assignment at generation and first load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:56:39 +02:00
|
|
|
|
var targetVelocity = float3(target.Velocity.x, 0, target.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,
|
Mining Cut 3: a chunk is a target, detected by reflected light
- Entity.Target is one slot of TargetRef, read-only from outside
(IReadOnlyReactiveProperty); Entity.SetTarget is its one writer. A chunk is
taken only while the entity can see it, so a rock in darkness cannot be
picked. Every handler, agent, turret and test writes through SetTarget.
- Entity.ChunkInfo (Q14 A): the value the per-tick sensor rule settles at,
computed on demand through Sensor.PassiveRate/Sensor.Gain -- sum of the
active sensors' passive rates over (sensors x TargetInfoDecay), capped at 1.
Stored nowhere; no per-tick loop over chunks. ChunkVisible is the detection
threshold through the chunk path.
- The lost-track rule reads through the chunk path: Activate's reconciliation
and a per-tick check of the one slot drop a chunk target that has gone dark.
- Entity.VisibleChunksInReach (Q13 A): visible chunks within the longest range
among active weapons -- the one reach rule for cycling and programmatic picks.
- Entity-only readers take .Entity: TargetedBy/TargetedByCount, target items,
the stance gate (a chunk has no stance), lock (Q12 A: never on a chunk),
turrets, Combat, Minion, FireControl (a chunk fires untargeted until Cut 4).
TargetRange and the arc gate read a chunk's planar position.
- ActionGameManager: the reticle and next/previous offer visible ships and
visible chunks in reach; nearest stays enemies-only; the target panel,
indicator and fire-control debug take TargetRef. EntityInstance reads .Entity.
- MiningCut3Tests pin parity, darkness, monotone brightness, wear and break,
reach, the chunk query, target survival and loss, TargetRef equality, lock,
and field-kind assignment at generation and first load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:56:39 +02:00
|
|
|
|
target.Position, targetVelocity
|
2021-01-27 16:14:41 +00:00
|
|
|
|
);
|
2026-10-15 18:39:28 +02:00
|
|
|
|
predictedPosition.y = target.RideHeight(predictedPosition.xz);
|
2026-10-07 12:33:20 +02:00
|
|
|
|
Entity.Aim = normalize(predictedPosition - Entity.Position);
|
2021-01-27 16:14:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
2026-10-07 12:33:20 +02:00
|
|
|
|
Entity.Aim = 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).
|
2026-10-08 14:18:41 +02:00
|
|
|
|
if (FireControl.AgentFires(x, Entity, target, Entity.ItemManager.GameplaySettings.AgentMinHitProbability))
|
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();
|
|
|
|
|
|
}
|
Mining Cut 3: a chunk is a target, detected by reflected light
- Entity.Target is one slot of TargetRef, read-only from outside
(IReadOnlyReactiveProperty); Entity.SetTarget is its one writer. A chunk is
taken only while the entity can see it, so a rock in darkness cannot be
picked. Every handler, agent, turret and test writes through SetTarget.
- Entity.ChunkInfo (Q14 A): the value the per-tick sensor rule settles at,
computed on demand through Sensor.PassiveRate/Sensor.Gain -- sum of the
active sensors' passive rates over (sensors x TargetInfoDecay), capped at 1.
Stored nowhere; no per-tick loop over chunks. ChunkVisible is the detection
threshold through the chunk path.
- The lost-track rule reads through the chunk path: Activate's reconciliation
and a per-tick check of the one slot drop a chunk target that has gone dark.
- Entity.VisibleChunksInReach (Q13 A): visible chunks within the longest range
among active weapons -- the one reach rule for cycling and programmatic picks.
- Entity-only readers take .Entity: TargetedBy/TargetedByCount, target items,
the stance gate (a chunk has no stance), lock (Q12 A: never on a chunk),
turrets, Combat, Minion, FireControl (a chunk fires untargeted until Cut 4).
TargetRange and the arc gate read a chunk's planar position.
- ActionGameManager: the reticle and next/previous offer visible ships and
visible chunks in reach; nearest stays enemies-only; the target panel,
indicator and fire-control debug take TargetRef. EntityInstance reads .Entity.
- MiningCut3Tests pin parity, darkness, monotone brightness, wear and break,
reach, the chunk query, target survival and loss, TargetRef equality, lock,
and field-kind assignment at generation and first load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:56:39 +02:00
|
|
|
|
Entity.SetTarget(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
|
|
|
|
}
|