Skip to content
Open
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
6 changes: 6 additions & 0 deletions Localization/en-us.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,12 @@ Localization
#MechJeb_Docking_label13 = Distance Dock: <<1>>
#MechJeb_Docking_label14 = Distance Dock Axis: <<1>>

#MechJeb_Docking_targetPort = Target port
#MechJeb_Docking_targetPortSelected = <<1>> (<<2>>/<<3>>)
#MechJeb_Docking_targetPortSelect = Select a port (<<1>> available)
#MechJeb_Docking_targetPortNone = No compatible undocked ports
#MechJeb_Docking_targetPortUnavailable = Available when the target vessel is nearby

#MechJeb_Docking_button = Dump Bounding Box Info

#MechJeb_Docking_checkbox1 = Autopilot enabled
Expand Down
6 changes: 6 additions & 0 deletions Localization/es-es.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,12 @@ Localization
#MechJeb_Docking_label13 = Distancia Muelle: <<1>>
#MechJeb_Docking_label14 = Distancia eje del muelle: <<1>>

#MechJeb_Docking_targetPort = Puerto objetivo
#MechJeb_Docking_targetPortSelected = <<1>> (<<2>>/<<3>>)
#MechJeb_Docking_targetPortSelect = Seleccionar un puerto (<<1>> disponibles)
#MechJeb_Docking_targetPortNone = No hay puertos compatibles sin acoplar
#MechJeb_Docking_targetPortUnavailable = Disponible cuando la nave objetivo esté cerca

#MechJeb_Docking_button = Información de la caja de volcado

#MechJeb_Docking_checkbox1 = Piloto automático habilitado
Expand Down
6 changes: 6 additions & 0 deletions Localization/fr-fr.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,12 @@ Localization
#MechJeb_Docking_label13 = Distance d'amarrage : <<1>>
#MechJeb_Docking_label14 = Distance de l'axe d'amarrage : <<1>>

#MechJeb_Docking_targetPort = Port cible
#MechJeb_Docking_targetPortSelected = <<1>> (<<2>>/<<3>>)
#MechJeb_Docking_targetPortSelect = Sélectionnez un port (<<1>> disponible(s))
#MechJeb_Docking_targetPortNone = Aucun port compatible non amarré
#MechJeb_Docking_targetPortUnavailable = Disponible lorsque le vaisseau cible est à proximité

#MechJeb_Docking_button = Enlever les infos de la boîte de collision

#MechJeb_Docking_checkbox1 = Pilote automatique activé
Expand Down
6 changes: 6 additions & 0 deletions Localization/ru.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,12 @@ Localization
#MechJeb_Docking_label13 = Расстояние до стыковочного порта: <<1>>
#MechJeb_Docking_label14 = Расстояние до оси стыковочного порта: <<1>>

#MechJeb_Docking_targetPort = Целевой порт
#MechJeb_Docking_targetPortSelected = <<1>> (<<2>>/<<3>>)
#MechJeb_Docking_targetPortSelect = Выберите порт (доступно: <<1>>)
#MechJeb_Docking_targetPortNone = Нет совместимых свободных стыковочных портов
#MechJeb_Docking_targetPortUnavailable = Доступно, когда целевой корабль находится поблизости

#MechJeb_Docking_button = Получить данные о размерах

#MechJeb_Docking_checkbox1 = Активировать автопилот
Expand Down
6 changes: 6 additions & 0 deletions Localization/zh-cn.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,12 @@ Localization
#MechJeb_Docking_label13 = 距离对接口: <<1>>
#MechJeb_Docking_label14 = 距离对接轴: <<1>>

#MechJeb_Docking_targetPort = 目标对接口
#MechJeb_Docking_targetPortSelected = <<1>> (<<2>>/<<3>>)
#MechJeb_Docking_targetPortSelect = 选择对接口(可用:<<1>>)
#MechJeb_Docking_targetPortNone = 没有兼容的未对接接口
#MechJeb_Docking_targetPortUnavailable = 目标飞船靠近时可用

#MechJeb_Docking_button = 转储飞船边界框信息

#MechJeb_Docking_checkbox1 = 启动自动驾驶
Expand Down
169 changes: 168 additions & 1 deletion MechJeb2/MechJebModuleDockingGuidance.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
extern alias JetBrainsAnnotations;
using System;
using System.Collections.Generic;
using System.Linq;
using KSP.Localization;
using UnityEngine;
Expand All @@ -11,6 +13,14 @@ public MechJebModuleDockingGuidance(MechJebCore core) : base(core) { }

private MechJebModuleDockingAutopilot autopilot;

private static readonly char[] DockingNodeTypeSeparator = { ',' };

// KSP changes a docking-port target back to its parent vessel while the
// target is farther than the part-target range. Keep the user's port
// choice locally so that cycling does not jump back to the first port.
private ModuleDockingNode selectedTargetPort;
private Vessel selectedTargetVessel;

public override void OnStart(PartModule.StartState state) => autopilot = Core.GetComputerModule<MechJebModuleDockingAutopilot>();

protected override void WindowGUI(int windowID)
Expand All @@ -31,7 +41,9 @@ protected override void WindowGUI(int windowID)
GuiUtils.YellowLabel); //Warning: You need to control the vessel from a docking port. Right click a docking port and select "Control from here"
}

if (!(Core.Target.Target is ModuleDockingNode))
DrawTargetPortSelector();

if (!(Core.Target.Target is ModuleDockingNode) && selectedTargetPort == null)
{
GUILayout.Label(Localizer.Format("#MechJeb_Docking_label3"),
GuiUtils.YellowLabel); //Warning: target is not a docking port. Right click the target docking port and select "Set as target"
Expand Down Expand Up @@ -138,6 +150,161 @@ protected override void WindowGUI(int windowID)
base.WindowGUI(windowID);
}

private void DrawTargetPortSelector()
{
Vessel targetVessel = GetTargetVessel();
if (targetVessel == null || targetVessel == Vessel)
{
selectedTargetPort = null;
selectedTargetVessel = null;
return;
}

if (selectedTargetVessel != targetVessel)
{
selectedTargetPort = null;
selectedTargetVessel = targetVessel;
}

GUILayout.BeginHorizontal();
GUILayout.Label(Localizer.Format("#MechJeb_Docking_targetPort"), GuiUtils.LayoutNoExpandWidth);

ModuleDockingNode referencePort = GetReferenceDockingPort();
if (!targetVessel.loaded || targetVessel.packed)
{
GUILayout.Label(Localizer.Format("#MechJeb_Docking_targetPortUnavailable"), GuiUtils.ArrowSelectorStyeGuiStyleExpand);
GUILayout.EndHorizontal();
return;
}

List<ModuleDockingNode> targetPorts = targetVessel.GetModules<ModuleDockingNode>()
.Where(port => IsAvailableTargetPort(port) && port.GetTransform() != null &&
ArePortsCompatible(referencePort, port))
.ToList();

ModuleDockingNode currentPort = Core.Target.Target as ModuleDockingNode;
if (currentPort != null && targetPorts.Contains(currentPort))
selectedTargetPort = currentPort;
else if (selectedTargetPort != null && !targetPorts.Contains(selectedTargetPort))
selectedTargetPort = null;

if (currentPort == null || !targetPorts.Contains(currentPort))
currentPort = selectedTargetPort;

int currentIndex = targetPorts.IndexOf(currentPort);
string portLabel;
if (currentIndex >= 0)
{
portLabel = Localizer.Format("#MechJeb_Docking_targetPortSelected", GetPortName(currentPort), currentIndex + 1,
targetPorts.Count);
}
else if (targetPorts.Count > 0)
{
portLabel = Localizer.Format("#MechJeb_Docking_targetPortSelect", targetPorts.Count);
}
else
{
portLabel = Localizer.Format("#MechJeb_Docking_targetPortNone");
}

bool guiEnabled = GUI.enabled;
GUI.enabled = guiEnabled && targetPorts.Count > 0 && !autopilot.Enabled;
if (GUILayout.Button("<", GUILayout.ExpandWidth(false)))
SelectTargetPort(targetPorts, currentIndex, -1);
GUILayout.Label(portLabel, GuiUtils.ArrowSelectorStyeGuiStyleExpand);
if (GUILayout.Button(">", GUILayout.ExpandWidth(false)))
SelectTargetPort(targetPorts, currentIndex, 1);
GUI.enabled = guiEnabled;
GUILayout.EndHorizontal();
}

private Vessel GetTargetVessel()
{
if (Core.Target.Target is Vessel targetVessel)
return targetVessel;

return Core.Target.Target?.GetVessel();
}

private ModuleDockingNode GetReferenceDockingPort()
{
Part referencePart = Vessel.GetReferenceTransformPart();
if (referencePart == null || Vessel.ReferenceTransform == null)
return null;

ModuleDockingNode referencePort = null;
double smallestAngle = 2;
foreach (ModuleDockingNode port in referencePart.Modules.OfType<ModuleDockingNode>())
{
Transform portTransform = port.GetTransform();
if (portTransform == null)
continue;

double angle = Vector3d.Angle(portTransform.forward, Vessel.ReferenceTransform.up);
if (angle < smallestAngle)
{
smallestAngle = angle;
referencePort = port;
}
}

return referencePort;
}

private static bool IsAvailableTargetPort(ModuleDockingNode port) =>
port != null && !IsDockedState(port.state);

private static bool IsDockedState(string state) =>
state != null && (state.StartsWith("Docked", StringComparison.Ordinal) || state == "PreAttached");

private static bool ArePortsCompatible(ModuleDockingNode sourcePort, ModuleDockingNode targetPort)
{
if (sourcePort == null || targetPort == null)
return false;

return ArePortsCompatible(sourcePort.nodeType, sourcePort.gendered, sourcePort.genderFemale, targetPort.nodeType,
targetPort.gendered, targetPort.genderFemale);
}

private static bool ArePortsCompatible(string sourceNodeType, bool sourceGendered, bool sourceFemale, string targetNodeType,
bool targetGendered, bool targetFemale)
{
if (sourceGendered != targetGendered)
return false;

if (sourceGendered && sourceFemale == targetFemale)
return false;

if (string.IsNullOrEmpty(sourceNodeType) || string.IsNullOrEmpty(targetNodeType))
return false;

string[] sourceNodeTypes = sourceNodeType.Split(DockingNodeTypeSeparator, StringSplitOptions.RemoveEmptyEntries);
string[] targetNodeTypes = targetNodeType.Split(DockingNodeTypeSeparator, StringSplitOptions.RemoveEmptyEntries);
return sourceNodeTypes.Intersect(targetNodeTypes).Any();
}

private void SelectTargetPort(IReadOnlyList<ModuleDockingNode> targetPorts, int currentIndex, int direction)
{
int nextIndex = currentIndex < 0
? direction > 0 ? 0 : targetPorts.Count - 1
: (currentIndex + direction + targetPorts.Count) % targetPorts.Count;

ModuleDockingNode targetPort = targetPorts[nextIndex];
selectedTargetPort = targetPort;
Core.Target.Set(targetPort);
// Keep KSP's global target in sync so other docking aids observe the port change immediately.
FlightGlobals.fetch.SetVesselTarget(targetPort);
}

private static string GetPortName(ModuleDockingNode port)
{
string portName = port.GetName();
if (!string.IsNullOrEmpty(portName))
return portName;

return port.part?.partInfo?.title ?? port.part?.name ?? "?";
}

protected override GUILayoutOption[] WindowOptions() => new[] { GuiUtils.LayoutWidth(300), GUILayout.Height(50) };

protected override void OnModuleDisabled()
Expand Down