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>
100 lines
No EOL
3.2 KiB
C#
100 lines
No EOL
3.2 KiB
C#
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* 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;
|
|
using MessagePack;
|
|
using Newtonsoft.Json;
|
|
using UniRx;
|
|
using CultMath;
|
|
using static CultMath.math;
|
|
using float3 = CultMath.float3;
|
|
|
|
[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);
|
|
}
|
|
}
|
|
|
|
public class TurretController : Behavior, IInitializableBehavior
|
|
{
|
|
private TurretControllerData _data;
|
|
private List<Weapon> _weapons = new List<Weapon>();
|
|
private float _shotSpeed;
|
|
private bool _predictShots;
|
|
|
|
public TurretController(TurretControllerData data, EquippedItem item) : base(data, item)
|
|
{
|
|
_data = data;
|
|
}
|
|
|
|
public TurretController(TurretControllerData data, ConsumableItemEffect item) : base(data, item)
|
|
{
|
|
_data = data;
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
foreach (var weapon in Entity.GetBehaviors<Weapon>())
|
|
{
|
|
_weapons.Add(weapon);
|
|
var vel = weapon.Evaluate(weapon.WeaponData.Velocity);
|
|
if (vel > .1f)
|
|
{
|
|
_predictShots = true;
|
|
_shotSpeed = vel;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override bool Execute(float dt)
|
|
{
|
|
if (Entity.Target.Value != null)
|
|
{
|
|
var diff = Entity.Target.Value.Position - Entity.Position;
|
|
if (_predictShots)
|
|
{
|
|
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);
|
|
var predictedPosition = first_order_intercept(
|
|
Entity.Position, float3.zero, _shotSpeed,
|
|
Entity.Target.Value.Position, targetVelocity
|
|
);
|
|
predictedPosition.y = Entity.Zone.GetHeight(predictedPosition.xz) + targetHullData.GridOffset;
|
|
Entity.LookDirection = normalize(predictedPosition - Entity.Position);
|
|
}
|
|
else
|
|
Entity.LookDirection = normalize(diff);
|
|
|
|
foreach (var x in _weapons)
|
|
{
|
|
// 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))
|
|
{
|
|
x.Activate();
|
|
}
|
|
else if (x.Firing)
|
|
x.Deactivate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var x in _weapons)
|
|
{
|
|
if (x.Firing)
|
|
x.Deactivate();
|
|
}
|
|
Entity.Target.Value = Entity.VisibleEnemies.FirstOrDefault(e => e is Ship);
|
|
}
|
|
return true;
|
|
}
|
|
} |