Vectored rows recorded: HardpointData.Row, ThrusterData.Slew, the judge's row rules
Thruster hardpoints state a gimbal arc in FiringArc and join a row by Row (key 7); the thruster design carries its slew (key 6). The judge refuses a Row on a non-thruster, rows whose members disagree on Rotation or FiringArc, and a fixed thruster, a mixed or split row, or a joint too short for the arc on a joint. Bind treats a thruster emitter's joint as it treats a gun mount's. Not yet verified: this commit has not been built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b6262467f0
commit
b1f4a2aa08
6 changed files with 284 additions and 14 deletions
|
|
@ -25,6 +25,11 @@ public class ThrusterData : BehaviorData
|
|||
|
||||
[Inspectable, CultInspectorAssetGuid, JsonProperty("Particles"), Key(5)]
|
||||
public string ParticlesPrefab;
|
||||
|
||||
// The gimbal's slew rate in degrees per second: the part's own stat, as a weapon's traverse is
|
||||
// (ruling mount-arc-gun-traverse). The swing limit is the hardpoint's FiringArc, the row its Row.
|
||||
[Inspectable, JsonProperty("slew"), Key(6), RuntimeInspectable]
|
||||
public PerformanceStat Slew = new PerformanceStat();
|
||||
|
||||
public override Behavior CreateInstance(EquippedItem item)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -566,7 +566,12 @@ public class HardpointData
|
|||
[Inspectable, JsonProperty("armor"), Key(5)] public float Armor;
|
||||
// Full arc width in degrees a weapon on this hardpoint may bear across; 0 means "use
|
||||
// GameplaySettings.FiringArc" (docs/fire-control-cut.md R6). Turrets author 360 (Q1).
|
||||
// On a Thruster hardpoint it is the full gimbal width: the row swings +-FiringArc/2 about Rotation, 0 is a
|
||||
// fixed thruster, and there is no GameplaySettings fallback.
|
||||
[Inspectable, JsonProperty("firingArc"), Key(6)] public float FiringArc;
|
||||
// Thruster hardpoints sharing a non-empty Row swing as one row; a Thruster hardpoint with FiringArc > 0 and no Row
|
||||
// is a row of one. Only a Thruster hardpoint may state one.
|
||||
[Inspectable, JsonProperty("row"), Key(7)] public string Row;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -262,6 +262,8 @@ public static class ShipAuthoringStore
|
|||
throw new InvalidOperationException($"{ship.Id}: hardpoint {hardpoint.Transform} has an unknown rotation {(int)hardpoint.Rotation}.");
|
||||
if (!float.IsFinite(hardpoint.Armor) || hardpoint.Armor < 0 || !float.IsFinite(hardpoint.FiringArc) || hardpoint.FiringArc < 0)
|
||||
throw new InvalidOperationException($"{ship.Id}: hardpoint {hardpoint.Transform} armor and firing arc must be finite and not negative.");
|
||||
if (!string.IsNullOrEmpty(hardpoint.Row) && hardpoint.Type != HardpointType.Thruster)
|
||||
throw new InvalidOperationException($"{ship.Id}: hardpoint {hardpoint.Transform} is a {hardpoint.Type} hardpoint, and only a Thruster hardpoint may state a row.");
|
||||
if (hardpoint.Shape?.Cells == null || !hardpoint.Shape.Cells.Cast<bool>().Any(occupied => occupied))
|
||||
throw new InvalidOperationException($"{ship.Id}: hardpoint {hardpoint.Transform} has no cells.");
|
||||
for (var cellX = 0; cellX < hardpoint.Shape.Width; cellX++)
|
||||
|
|
@ -285,6 +287,16 @@ public static class ShipAuthoringStore
|
|||
if (mountRole != null && mountAnchor.Role != mountRole)
|
||||
throw new InvalidOperationException($"{ship.Id}: {hardpoint.Type} hardpoint {hardpoint.Transform} needs a {mountRole} anchor of that id.");
|
||||
}
|
||||
foreach (var row in mounts.Values.Where(hardpoint => hardpoint.Type == HardpointType.Thruster && !string.IsNullOrEmpty(hardpoint.Row))
|
||||
.GroupBy(hardpoint => hardpoint.Row, StringComparer.Ordinal))
|
||||
{
|
||||
var lead = row.First();
|
||||
foreach (var hardpoint in row)
|
||||
if (hardpoint.Rotation != lead.Rotation || hardpoint.FiringArc != lead.FiringArc)
|
||||
throw new InvalidOperationException($"{ship.Id}: thruster hardpoints {lead.Transform} and {hardpoint.Transform} are in row {row.Key} and must share Rotation and FiringArc.");
|
||||
if (!(lead.FiringArc > 0f) || lead.FiringArc > 360f)
|
||||
throw new InvalidOperationException($"{ship.Id}: thruster row {row.Key} must state a gimbal FiringArc above 0 and at most 360.");
|
||||
}
|
||||
foreach (var anchor in anchors)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(anchor.ParentId) && !mounts.ContainsKey(anchor.ParentId))
|
||||
|
|
@ -371,12 +383,12 @@ public static class ShipAuthoringStore
|
|||
|
||||
foreach (var anchor in anchors.Where(anchor => !string.IsNullOrEmpty(anchor.Joint)))
|
||||
{
|
||||
if (anchor.Role == "thruster-emitter" || anchor.Role == "radiator-mesh")
|
||||
throw new InvalidOperationException($"{ship.Id}: {anchor.Role} anchor {anchor.Id} may not ride a joint (follow-up gimballed-thrusters).");
|
||||
if (anchor.Role != "weapon-mount")
|
||||
throw new InvalidOperationException($"{ship.Id}: {anchor.Role} anchor {anchor.Id} may not name a joint; only a weapon mount rides one.");
|
||||
if (anchor.Role == "radiator-mesh")
|
||||
throw new InvalidOperationException($"{ship.Id}: {anchor.Role} anchor {anchor.Id} may not ride a joint.");
|
||||
if (anchor.Role != "weapon-mount" && anchor.Role != "thruster-emitter")
|
||||
throw new InvalidOperationException($"{ship.Id}: {anchor.Role} anchor {anchor.Id} may not name a joint; only a weapon mount or a thruster emitter rides one.");
|
||||
if (!byId.ContainsKey(anchor.Joint))
|
||||
throw new InvalidOperationException($"{ship.Id}: weapon mount {anchor.Id} names an unknown joint.");
|
||||
throw new InvalidOperationException($"{ship.Id}: {anchor.Role} anchor {anchor.Id} names an unknown joint.");
|
||||
}
|
||||
|
||||
var ridden = anchors.Where(anchor => anchor.Role == "weapon-mount" && !string.IsNullOrEmpty(anchor.Joint))
|
||||
|
|
@ -394,6 +406,30 @@ public static class ShipAuthoringStore
|
|||
if (reach < lead.FiringArc)
|
||||
throw new InvalidOperationException($"{ship.Id}: joint {group.Key} chain reaches {reach} degrees of yaw, short of the {lead.FiringArc} degree firing arc of {lead.Transform}.");
|
||||
}
|
||||
|
||||
// A thruster row is a gimbal: its hardpoints' stated FiringArc is how far the row swings, so a row on a joint rides it
|
||||
// whole and alone, and the joint's chain must be able to sweep that arc. A hardpoint with no Row is a row of one.
|
||||
var thrusters = mounts.Values.Where(hardpoint => hardpoint.Type == HardpointType.Thruster).ToArray();
|
||||
string RowOf(HardpointData hardpoint) => string.IsNullOrEmpty(hardpoint.Row) ? null : hardpoint.Row;
|
||||
var jointOf = anchors.Where(anchor => anchor.Role == "thruster-emitter").ToDictionary(anchor => anchor.Id, anchor => anchor.Joint ?? "", StringComparer.Ordinal);
|
||||
var tilted = thrusters.Where(hardpoint => jointOf.TryGetValue(hardpoint.Transform, out var joint) && joint.Length > 0).ToArray();
|
||||
foreach (var hardpoint in tilted)
|
||||
if (!(hardpoint.FiringArc > 0f))
|
||||
throw new InvalidOperationException($"{ship.Id}: thruster hardpoint {hardpoint.Transform} rides a joint, so it must state its firing arc above 0; a fixed thruster may not ride a joint.");
|
||||
foreach (var onJoint in tilted.GroupBy(hardpoint => jointOf[hardpoint.Transform], StringComparer.Ordinal))
|
||||
{
|
||||
var lead = onJoint.First();
|
||||
foreach (var hardpoint in onJoint)
|
||||
if (RowOf(hardpoint) != RowOf(lead) || RowOf(hardpoint) == null && hardpoint != lead)
|
||||
throw new InvalidOperationException($"{ship.Id}: joint {onJoint.Key} carries thruster hardpoints {lead.Transform} and {hardpoint.Transform} of different rows; a joint carries one row.");
|
||||
var members = RowOf(lead) == null ? new[] { lead } : thrusters.Where(hardpoint => RowOf(hardpoint) == RowOf(lead)).ToArray();
|
||||
foreach (var member in members)
|
||||
if (!jointOf.TryGetValue(member.Transform, out var memberJoint) || memberJoint != onJoint.Key)
|
||||
throw new InvalidOperationException($"{ship.Id}: thruster hardpoint {member.Transform} is in the row of {lead.Transform} on joint {onJoint.Key} and must ride it too; a row rides one joint whole.");
|
||||
var reach = YawReach(ship, onJoint.Key);
|
||||
if (reach < lead.FiringArc)
|
||||
throw new InvalidOperationException($"{ship.Id}: joint {onJoint.Key} chain reaches {reach} degrees of yaw, short of the {lead.FiringArc} degree gimbal arc of {lead.Transform}.");
|
||||
}
|
||||
}
|
||||
|
||||
// The yaw a driven joint's chain can sweep: the summed range of every degree of freedom whose axis is yaw, capped at a turn.
|
||||
|
|
|
|||
|
|
@ -193,8 +193,8 @@ public static class ShipModCatalog
|
|||
}
|
||||
|
||||
// The rig the record states must be the rig the model has: every joint is a node, each joint's Parent and each mount's
|
||||
// Joint is the nearest joint above its node, ancestors without an id being walked through by ReadNodeIds, and a thruster or radiator
|
||||
// mesh sits under no joint (follow-up gimballed-thrusters).
|
||||
// Joint is the nearest joint above its node, ancestors without an id being walked through by ReadNodeIds, and a radiator
|
||||
// mesh sits under no joint.
|
||||
private static void BindRig(ShipAuthoring ship, Dictionary<string, (uint Index, bool HasMesh, int Parent)> nodes)
|
||||
{
|
||||
var joints = new HashSet<string>((ship.Joints ?? new List<ShipJoint>()).Select(joint => joint.Id), StringComparer.Ordinal);
|
||||
|
|
@ -220,9 +220,9 @@ public static class ShipModCatalog
|
|||
{
|
||||
var above = JointAbove(anchor.ModelNodeId);
|
||||
if (string.Equals(above ?? "", anchor.Joint ?? "", StringComparison.Ordinal)) continue;
|
||||
throw new InvalidOperationException(anchor.Role == "weapon-mount"
|
||||
? $"{ship.Id}: weapon mount {anchor.Id} must name the nearest joint above its model node as its Joint."
|
||||
: $"{ship.Id}: {anchor.Role} anchor {anchor.Id} sits under a joint in the model (follow-up gimballed-thrusters).");
|
||||
throw new InvalidOperationException(anchor.Role == "radiator-mesh"
|
||||
? $"{ship.Id}: {anchor.Role} anchor {anchor.Id} sits under a joint in the model."
|
||||
: $"{ship.Id}: {(anchor.Role == "weapon-mount" ? "weapon mount" : "thruster emitter")} {anchor.Id} must name the nearest joint above its model node as its Joint.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue