Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions FreeIva/InternalModules/Hatches/Hatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,91 @@ public override void OnAwake()
}
}

// Auto-detect airlockName per-instance at flight time against the actual part's airlocks.
// Cannot do this at prefab-load time (OnLoad_Finalize) because GetPartPrefabForInternal
// returns only the first part using the internal, polluting the shared prefab for every
// other part that shares the same internal (same root cause as attachNodeId pollution).
// Only auto-detect for hatches without a HatchConfig (i.e. those that need auto-configuration).
if (airlockName == string.Empty && !isEvaHatch && GetComponent<HatchConfig>() == null)
{
Transform[] airlocks = part.airlock == null ? null : part.FindModelTransformsWithTag(AIRLOCK_TAG);
if (airlocks != null && airlocks.Length > 0)
{
var countByName = new Dictionary<string, int>(airlocks.Length);
string[] airlockNames = new string[airlocks.Length];
for (int i = 0; i < airlocks.Length; i++)
{
string name = airlocks[i].name;
if (countByName.TryGetValue(name, out int count))
{
airlockNames[i] = name + "," + count;
countByName[name] = count + 1;
}
else
{
airlockNames[i] = name;
countByName[name] = 1;
}
}

float bestDistSq = 1f;
int bestIndex = -1;
Quaternion internalToPartSpace = Quaternion.Inverse(InternalModuleFreeIva.x_partToInternalSpace);
for (int i = 0; i < airlocks.Length; i++)
{
// NOTE: some airlocks have non-identity scale so InverseTransformPoint doesn't work
Vector3 hatchInPartSpace = internalToPartSpace * transform.position;
Vector3 delta = hatchInPartSpace - airlocks[i].transform.position;
Vector3 hatchInAirlockSpace = airlocks[i].transform.InverseTransformDirection(delta);
if (hatchInAirlockSpace.z >= 0 && hatchInAirlockSpace.z <= 1)
{
hatchInAirlockSpace.z = 0;
if (hatchInAirlockSpace.sqrMagnitude <= bestDistSq)
{
bestDistSq = hatchInAirlockSpace.sqrMagnitude;
bestIndex = i;
}
}
}

if (bestIndex >= 0)
{
airlockName = airlockNames[bestIndex];
Log.Debug($"INTERNAL '{internalModel.internalName}' hatch PROP '{internalProp.propName}' auto-detected airlock '{airlockName}' for part '{part.partInfo.name}'");
}
}
}

if (airlockName != string.Empty || isEvaHatch)
{
airlockTransform = FindAirlock(part, airlockName);
}

// Auto-detect the attach node for this hatch against the actual flight part.
// This must run per-instance at flight time rather than at prefab load time,
// because multiple parts can share the same internal: detecting on the prefab
// using the first matching part would write an incorrect attachNodeId into the
// shared prefab, which every subsequent part then inherits.
if (attachNodeId == string.Empty && tubeTransform != null)
{
foreach (var attachNode in part.attachNodes)
{
Vector3 attachNodeInternalSpace = InternalModuleFreeIva.x_partToInternalSpace * attachNode.position;
Vector3 localPosition = tubeTransform.InverseTransformPoint(attachNodeInternalSpace);

if (localPosition.y >= -1 && localPosition.y <= 0)
{
localPosition.y = 0;
if (localPosition.sqrMagnitude < 0.1f)
{
Log.Debug($"INTERNAL '{internalModel.internalName}' hatch PROP '{internalProp.propName}' auto-detected attachnode '{attachNode.id}' for part '{part.partInfo.name}'");
attachNodeId = attachNode.id;
break;
}
}
}
}

var internalModule = InternalModuleFreeIva.GetForModel(internalModel);
if (internalModule == null)
{
Expand Down
94 changes: 4 additions & 90 deletions FreeIva/InternalModules/InternalModuleFreeIva.cs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ private void OnLoad_Windows(ConfigNode node)
}
}

static Quaternion x_partToInternalSpace = Quaternion.Euler(90, 0, 180);
internal static Quaternion x_partToInternalSpace = Quaternion.Euler(90, 0, 180);
static Quaternion x_internalToPartSpace = Quaternion.Inverse(x_partToInternalSpace);

void OnLoad_Finalize()
Expand All @@ -517,7 +517,6 @@ void OnLoad_Finalize()
Part part = ModuleFreeIva.GetPartPrefabForInternal(internalModel.internalName);

List<FreeIvaHatch> hatches = new List<FreeIvaHatch>();
List<string> boundAirlockNames = new List<string>();

foreach (var prop in internalModel.props)
{
Expand All @@ -526,11 +525,6 @@ void OnLoad_Finalize()
{
var hatchConfig = prop.GetComponent<HatchConfig>();

if (hatch.airlockName != string.Empty)
{
boundAirlockNames.Add(hatch.airlockName);
}

if (hatchConfig != null || hatch.cutoutTargetTransformName != string.Empty)
{
AddPropCut(hatch);
Expand All @@ -547,89 +541,9 @@ void OnLoad_Finalize()
AddPropCut(hatch);
}

// for attachnodes...
if (part != null && hatch.tubeTransform != null && hatch.attachNodeId == string.Empty)
{
foreach (var attachNode in part.attachNodes)
{
Vector3 attachNodeInternalSpace = x_partToInternalSpace * attachNode.position;
Vector3 localPosition = hatch.tubeTransform.InverseTransformPoint(attachNodeInternalSpace);

if (localPosition.y >= -1 && localPosition.y <= 0)
{
localPosition.y = 0;
if (localPosition.sqrMagnitude < 0.1f)
{
Log.Debug($"INTERNAL '{internalModel.internalName}' hatch PROP '{prop.propName}' at {prop.transform.position} auto-detected attachnode '{attachNode.id}'");
hatch.attachNodeId = attachNode.id;
break;
}
}
}
}
}
}
}

// auto-configure airlocks
// since hatches do not have a defined "out" direction (different hatches are authored in different orientations), we do this in part space (because the airlocks DO have a defined "in" direction)
Transform[] airlocks = part?.airlock == null ? null : part.FindModelTransformsWithTag(FreeIvaHatch.AIRLOCK_TAG);
string[] airlockNames = airlocks == null ? null : new string[airlocks.Length];

if (airlocks != null)
{
// get a unique name for each airlock
var countByName = new Dictionary<string, int>(airlocks.Length);
for (int i = 0; i < airlocks.Length; i++)
{
string name = airlocks[i].name;
if (countByName.TryGetValue(name, out int count))
{
airlockNames[i] = name + "," + count;
++count;
countByName[name] = count;
}
else
{
airlockNames[i] = name;
countByName[name] = 1;
}
}

for (int airlockIndex = 0; airlockIndex < airlocks.Length; airlockIndex++)
{
var airlock = airlocks[airlockIndex];
float bestDistanceSquared = 1; // we're pretty generous with positioning (compared to attachnodes) because these don't need to line up perfectly
FreeIvaHatch bestHatch = null;

if (boundAirlockNames.Contains(airlockNames[airlockIndex])) continue;

foreach (var hatch in hatches)
{
if (hatch.airlockName != string.Empty) continue;

Vector3 hatchInPartSpace = x_internalToPartSpace * hatch.transform.position;

// NOTE: some airlocks have non-identity scale (e.g. mk2LanderCan from restock) so InverseTransformPoint doesn't work
Vector3 airlockToHatch = hatchInPartSpace - airlock.transform.position;
Vector3 hatchInAirlockSpace = airlock.transform.InverseTransformDirection(airlockToHatch);

// airlock's +Z is toward the part (the way the kerbal faces)
if (hatchInAirlockSpace.z >= 0 && hatchInAirlockSpace.z <= 1)
{
hatchInAirlockSpace.z = 0;
if (hatchInAirlockSpace.sqrMagnitude <= bestDistanceSquared)
{
bestDistanceSquared = hatchInAirlockSpace.sqrMagnitude;
bestHatch = hatch;
}
}
}

if (bestHatch != null)
{
bestHatch.airlockName = airlockNames[airlockIndex];
Log.Debug($"INTERNAL '{internalModel.internalName}' hatch PROP '{bestHatch.internalProp.propName}' at {bestHatch.internalProp.transform.position} auto-detected airlock '{bestHatch.airlockName}'");
// attach node and airlock auto-detection are deferred to flight time (OnAwake in Hatch.cs)
// so that each part instance detects against its own nodes/airlocks, avoiding
// shared-prefab pollution when multiple parts use the same internal
}
}
}
Expand Down
1 change: 0 additions & 1 deletion GameData/FreeIva/Squad/PartCfgs/dockingPort.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ INTERNAL
name = HatchConfig
attachNodeId = top
dockingPortNodeName = dockingNode
airlockName = Airlock
}
}
}
1 change: 0 additions & 1 deletion GameData/FreeIva/Squad/PartCfgs/dockingPortJr.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ INTERNAL
name = HatchConfig
attachNodeId = top
dockingPortNodeName = dockingNode
airlockName = Airlock
hideDoorWhenConnected = true
}
}
Expand Down
1 change: 0 additions & 1 deletion GameData/FreeIva/Squad/PartCfgs/dockingPortShielded.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ INTERNAL
name = HatchConfig
dockingPortNodeName = dockingNode
requireDeploy = true
airlockName = Airlock
}
}
PROP
Expand Down
1 change: 0 additions & 1 deletion GameData/FreeIva/Squad/PartCfgs/dockingPortSr.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ INTERNAL
name = HatchConfig
attachNodeId = top
dockingPortNodeName = dockingNode
airlockName = Airlock
hideDoorWhenConnected = true
}
}
Expand Down