One placement rule for hardpoint gear

Soul's S3 and the S2 ruling ("B is the design intent, saving slots in a
hardpoint can be a valid tradeoff for crowded ships if you really need to fit
an extra tool"). Entity.HardpointAt is the one placement rule: at an origin,
the item lies within a hardpoint of its type at that hardpoint's rotation
(HardpointData.TakesAt), its own cells are free, and that hardpoint holds no
other hardpoint item. The origin no longer has to be a hardpoint cell, and
TryFindSpace tries every offset inside a hardpoint, the offsets the fit rule
tries, so an L-shaped hardpoint takes its L design instead of generation
throwing. Leftover hardpoint cells may hold general gear, and a hardpoint still
holds one hardpoint item.

Tests: an L hardpoint takes its L design by hand and in generation (Soul's
P11); a turret's gun goes back beside the tool gear in its leftover cells
(P21); a second gun in a roomy hardpoint is refused; a saved entity reloads to
the same loadout with its items in reverse order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-30 23:13:37 +02:00
commit ff2be8e26f
2 changed files with 132 additions and 16 deletions

View file

@ -790,9 +790,6 @@ public abstract class Entity
// Check whether the given item will fit when its origin is placed at the given coordinate
private bool ItemFits(EquippableItemData itemData, HullData hullData, EquippableItem item, int2 hullCoord)
{
// If the given coordinate isn't even in the ship it obviously won't fit
if (!hullData.Shape[hullCoord]) return false;
// Items without specific hardpoints on the ship can be freely rotated and placed anywhere
if (itemData.HardpointType == HardpointType.Tool)
{
@ -809,24 +806,34 @@ public abstract class Entity
}
else
{
var hardpoint = Hardpoints[hullCoord.x, hullCoord.y];
// If there's no hardpoint there, it won't fit
var hardpoint = HardpointAt(itemData, hullData, hullCoord);
if (hardpoint == null) return false;
// The one fit rule, at this placement: the item, aligned to the hardpoint's rotation, lies within its cells
if (!hardpoint.TakesAt(itemData, hullCoord - hardpoint.Position)) return false;
item.Rotation = hardpoint.Rotation;
// One item per hardpoint: every cell of it is free (and so every cell of the item)
foreach (var v in hullData.Shape.Inset(hardpoint.Shape, hardpoint.Position).Coordinates)
if (GearOccupancy[v.x, v.y] != null)
return false;
}
return true;
}
// The one placement rule for hardpoint gear. At this origin the item lies within a hardpoint of its type, turned to
// that hardpoint's rotation (HardpointData.TakesAt, the fit rule); its own cells are free, since a hardpoint's
// leftover cells may hold general gear (operator, 2026-09-30: "saving slots in a hardpoint can be a valid tradeoff
// for crowded ships"); and that hardpoint holds no other hardpoint item, since it holds one at most.
private HardpointData HardpointAt(EquippableItemData itemData, HullData hullData, int2 hullCoord)
{
foreach (var hardpoint in hullData.Hardpoints)
{
if (!hardpoint.TakesAt(itemData, hullCoord - hardpoint.Position)) continue;
var ownCellsFree = itemData.Shape.Coordinates
.Select(v => hullCoord + itemData.Shape.Rotate(v, hardpoint.Rotation))
.All(c => GearOccupancy[c.x, c.y] == null);
var holdsAnother = hardpoint.Shape.Coordinates
.Select(v => GearOccupancy[hardpoint.Position.x + v.x, hardpoint.Position.y + v.y])
.Any(occupant => occupant != null && occupant.Data.HardpointType != HardpointType.Tool);
if (ownCellsFree && !holdsAnother) return hardpoint;
}
return null;
}
// Check whether the given item will fit when its origin is placed at the given coordinate on the hull
public bool ItemFits(EquippableItem item, int2 hullCoord)
{
@ -867,13 +874,15 @@ public abstract class Entity
// Search the ship for an empty hardpoint that matches the type and shape of the item
else
{
// Every offset within a hardpoint of the item's type, the same offsets the fit rule tries
foreach (var hardpoint in hullData.Hardpoints)
{
if(hardpoint.Type == itemData.HardpointType)
{
foreach (var hardpointCoord in hardpoint.Shape.Coordinates)
for (var x = 0; x < hardpoint.Shape.Width; x++)
for (var y = 0; y < hardpoint.Shape.Height; y++)
{
var hullCoord2 = hardpoint.Position + hardpointCoord;
var hullCoord2 = hardpoint.Position + int2(x, y);
if (ItemFits(itemData, hullData, item, hullCoord2))
{
hullCoord = hullCoord2;