Require a targeting system on every armed loadout; AI aims through reveal

LoadoutGenerator.FillInterior equips a targeting-system product,
required: true, on any entity EquipHardpoints already gave a weapon --
Q4's "not an upgrade" ruling made structural. CombatState now selects
the target's most dangerous revealed weapon item each tick through the
same IsRevealed predicate and TrySelectTargetItem writer the player path
uses, instead of never touching TargetItem at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-19 13:02:37 +02:00
commit 52a18efcdc
2 changed files with 39 additions and 0 deletions

View file

@ -33,6 +33,25 @@ public class CombatState : BaseState
var toTarget = target.Position - _agent.Ship.Position;
var targetDistance = length(toTarget);
// Cut 2 (docs/fire-control-cut.md): aim at whichever revealed weapon on the target hits hardest at
// this range, or clear the aim point when nothing qualifies -- the same predicate
// (FireControl.IsRevealed) and the same writer (TrySelectTargetItem) the player path uses.
EquippedItem bestTargetItem = null;
var bestTargetDps = float.MinValue;
foreach (var targetItem in target.Equipment)
{
if (!(targetItem.Behaviors.FirstOrDefault(b => b is Weapon) is Weapon targetWeapon)) continue;
if (!FireControl.IsRevealed(_agent.Ship, targetItem)) continue;
var dps = targetWeapon.RangeDamagePerSecond(targetDistance);
if (dps > bestTargetDps)
{
bestTargetDps = dps;
bestTargetItem = targetItem;
}
}
_agent.Ship.TrySelectTargetItem(bestTargetItem);
for (var i = 0; i < _agent.Ship.WeaponGroups.Length; i++)
{
var group = _agent.Ship.WeaponGroups[i];

View file

@ -258,6 +258,26 @@ public class LoadoutGenerator
capacitor.Rotation = capacitorRotation;
if (!entity.TryEquip(capacitor, capacitorPosition))
throw new InvalidLoadoutException("Failed to equip selected capacitor!");
// Cut 2 (docs/fire-control-cut.md, Q4): required equipment for anything that can fire -- unaided
// fire is a last resort, not an alternative loadout choice. EquipHardpoints runs before FillInterior
// (OutfitEntity), so entity.Weapons is already populated here. Zenith and anything else with no
// weapon hardpoints gets none.
if (entity.Weapons.Any())
{
emptyShape = entity.UnoccupiedSpace;
var (targetingProduct, targetingData) = RandomProduct<GearData>(2, item =>
item.Behaviors.Any(b => b is TargetingSystemData) &&
item.Shape.FitsWithin(emptyShape, out _, out _), required: true);
if (targetingData == null) throw new InvalidLoadoutException("No compatible targeting system found for entity!");
targetingData.Shape.FitsWithin(emptyShape, out var targetingRotation, out var targetingPosition);
var targetingSystem = ItemManager.CreateInstance(targetingProduct) as EquippableItem;
targetingSystem.Rotation = targetingRotation;
if (!entity.TryEquip(targetingSystem, targetingPosition))
throw new InvalidLoadoutException("Failed to equip selected targeting system!");
}
}
}