/// <inheritdoc/> public override void AlignTo(Transform target) { if (target == null) { AlignTransforms.SnapAlign(model, pipeAttach, parkAt); model.gameObject.SetActive(true); } else { base.AlignTo(target); } }
/// <summary>Aligns node against the target.</summary> /// <param name="target"> /// The target object. Can be <c>null</c> in which case the node model will be hidden. /// </param> /// <include file="KSPDevUtilsAPI_HelpIndex.xml" path="//item[@name='M:KSPDev.AlignTransforms.SnapAlign']/*"/> public virtual void AlignTo(Transform target) { if (target != null) { AlignTransforms.SnapAlign(model, partAttach, target); model.gameObject.SetActive(true); } else { model.gameObject.SetActive(false); } }
/// <inheritdoc/> protected override void RestoreOtherPeer() { base.RestoreOtherPeer(); if (linkTarget != null) { // Do it before physics kicked-in to have the cable distance calculated correctly. AlignTransforms.SnapAlign( connectorModelObj, connectorPartAnchor, linkTarget.nodeTransform); // Only do it for the visual improvements of the vessel loading. The connector state machine // will handle it right, but it only happens when the physics is started. linkRenderer.StartRenderer(partCableAnchor, connectorCableAnchor); } }
/// <summary>Updates the node's state to the target transform.</summary> /// <param name="target"> /// The transfrom to align the node to. If <c>null</c>, then the model will be "parked". /// </param> /// <seealso cref="parkAttach"/> public void AlignToTransform(Transform target) { if (target != null) { rootModel.gameObject.SetActive(true); AlignTransforms.SnapAlign(rootModel, partAttach, target); rootModel.parent = target; } else { rootModel.parent = parkRootObject; if (parkAttach == null) { rootModel.gameObject.SetActive(false); } else { rootModel.gameObject.SetActive(true); AlignTransforms.SnapAlign(rootModel, pipeAttach, parkAttach); } } }
/// <inheritdoc/> protected override void SetupStateMachine() { base.SetupStateMachine(); linkStateMachine.onAfterTransition += (start, end) => UpdateContextMenu(); linkStateMachine.AddStateHandlers( LinkState.Linked, enterHandler: oldState => { var module = linkTarget as PartModule; PartModuleUtils.InjectEvent(this, DetachConnectorEvent, module); PartModuleUtils.AddEvent(module, _grabConnectorEventInject); }, leaveHandler: newState => { var module = linkTarget as PartModule; PartModuleUtils.WithdrawEvent(this, DetachConnectorEvent, module); PartModuleUtils.DropEvent(module, _grabConnectorEventInject); }); linkStateMachine.AddStateHandlers( LinkState.NodeIsBlocked, enterHandler: oldState => { if (decoupleIncompatibleTargets && coupleNode != null && coupleNode.attachedPart != null) { HostedDebugLog.Warning(this, "Decouple incompatible part from the node: {0}", coupleNode.FindOpposingNode().attachedPart); UISoundPlayer.instance.Play(KASAPI.CommonConfig.sndPathBipWrong); ShowStatusMessage( CannotLinkToPreAttached.Format(coupleNode.attachedPart), isError: true); KASAPI.LinkUtils.DecoupleParts(part, coupleNode.attachedPart); } }, callOnShutdown: false); // The default state is "Locked". All the enter state handlers rely on it, and all the exit // state handlers reset the state back to the default. connectorStateMachine = new SimpleStateMachine <ConnectorState>(); connectorStateMachine.onAfterTransition += (start, end) => { if (end != null) // Do nothing on state machine shutdown. { persistedIsConnectorLocked = isConnectorLocked; if (end == ConnectorState.Locked) { KASAPI.AttachNodesUtils.AddNode(part, coupleNode); } else if (coupleNode.attachedPart == null) { KASAPI.AttachNodesUtils.DropNode(part, coupleNode); } UpdateContextMenu(); } HostedDebugLog.Info(this, "Connector state changed: {0} => {1}", start, end); }; connectorStateMachine.SetTransitionConstraint( ConnectorState.Docked, new[] { ConnectorState.Plugged, ConnectorState.Locked, // External detach. }); connectorStateMachine.SetTransitionConstraint( ConnectorState.Locked, new[] { ConnectorState.Deployed, ConnectorState.Plugged, ConnectorState.Docked, // External attach. }); connectorStateMachine.SetTransitionConstraint( ConnectorState.Deployed, new[] { ConnectorState.Locked, ConnectorState.Plugged, }); connectorStateMachine.SetTransitionConstraint( ConnectorState.Plugged, new[] { ConnectorState.Deployed, ConnectorState.Docked, }); connectorStateMachine.AddStateHandlers( ConnectorState.Locked, enterHandler: oldState => { SaveConnectorModelPosAndRot(); if (oldState.HasValue) // Skip when restoring state. { UISoundPlayer.instance.Play(sndPathLockConnector); } }, leaveHandler: newState => SaveConnectorModelPosAndRot(saveNonPhysical: newState == ConnectorState.Deployed), callOnShutdown: false); connectorStateMachine.AddStateHandlers( ConnectorState.Docked, enterHandler: oldState => { SaveConnectorModelPosAndRot(); cableJoint.SetLockedOnCouple(true); // Align the docking part to the nodes if it's a separate vessel. if (oldState.HasValue && linkTarget.part.vessel != vessel) { AlignTransforms.SnapAlignNodes(linkTarget.coupleNode, coupleNode); linkJoint.SetCoupleOnLinkMode(true); UISoundPlayer.instance.Play(sndPathDockConnector); } }, leaveHandler: newState => { cableJoint.SetLockedOnCouple(false); SaveConnectorModelPosAndRot(saveNonPhysical: newState == ConnectorState.Deployed); linkJoint.SetCoupleOnLinkMode(false); }, callOnShutdown: false); connectorStateMachine.AddStateHandlers( ConnectorState.Plugged, enterHandler: oldState => SaveConnectorModelPosAndRot(), leaveHandler: newState => SaveConnectorModelPosAndRot(saveNonPhysical: newState == ConnectorState.Deployed), callOnShutdown: false); connectorStateMachine.AddStateHandlers( ConnectorState.Deployed, enterHandler: oldState => StartPhysicsOnConnector(), leaveHandler: newState => StopPhysicsOnConnector(), callOnShutdown: false); }
/// <summary>Intializes the connector model object and its anchors.</summary> /// <remarks> /// <para> /// If the connector model is not found then a stub object will be created. There will be no visual /// representation but the overall functionality of the winch should keep working. /// </para> /// <para> /// If the connector doesn't have the anchors then the missed ones will be created basing on the /// provided position/rotation. If the config file doesn't provide anything then the anchors will /// have a zero position and a random rotation. /// </para> /// </remarks> void LoadOrCreateConnectorModel() { var ConnectorModelName = "ConnectorModel" + part.Modules.IndexOf(this); var ConnectorParkAnchorName = "ConnectorParkAnchor" + part.Modules.IndexOf(this); const string CableAnchorName = "CableAnchor"; const string PartAnchorName = "PartAnchor"; if (!PartLoader.Instance.IsReady()) { // Make the missing models and set the proper hierarchy. connectorModelObj = Hierarchy.FindPartModelByPath(part, connectorModel); connectorCableAnchor = connectorCableAttachAt != "" ? Hierarchy.FindPartModelByPath(part, connectorCableAttachAt) : null; connectorPartAnchor = connectorPartAttachAt != "" ? Hierarchy.FindPartModelByPath(part, connectorPartAttachAt) : null; if (connectorModelObj == null) { HostedDebugLog.Error(this, "Cannot find a connector model: {0}", connectorModel); // Fallback to not have the whole code to crash. connectorModelObj = new GameObject().transform; } connectorModelObj.name = ConnectorModelName; connectorModelObj.parent = nodeTransform; if (connectorCableAnchor == null) { if (connectorCableAttachAt != "") { HostedDebugLog.Error( this, "Cannot find cable anchor transform: {0}", connectorCableAttachAt); } connectorCableAnchor = new GameObject().transform; var posAndRot = PosAndRot.FromString(connectorCableAttachAtPosAndRot); Hierarchy.MoveToParent(connectorCableAnchor, connectorModelObj, newPosition: posAndRot.pos, newRotation: posAndRot.rot); } connectorCableAnchor.name = CableAnchorName; connectorCableAnchor.parent = connectorModelObj; if (connectorPartAnchor == null) { if (connectorPartAttachAt != "") { HostedDebugLog.Error( this, "Cannot find part anchor transform: {0}", connectorPartAttachAt); } connectorPartAnchor = new GameObject().transform; var posAndRot = PosAndRot.FromString(connectorPartAttachAtPosAndRot); Hierarchy.MoveToParent(connectorPartAnchor, connectorModelObj, newPosition: posAndRot.pos, newRotation: posAndRot.rot); } connectorPartAnchor.name = PartAnchorName; connectorPartAnchor.parent = connectorModelObj; partCableAnchor = new GameObject(ConnectorParkAnchorName).transform; Hierarchy.MoveToParent( partCableAnchor, nodeTransform, newPosition: connectorParkPositionOffset); } else { connectorModelObj = nodeTransform.Find(ConnectorModelName); connectorCableAnchor = connectorModelObj.Find(CableAnchorName); connectorPartAnchor = connectorModelObj.Find(PartAnchorName); partCableAnchor = nodeTransform.Find(ConnectorParkAnchorName); } AlignTransforms.SnapAlign(connectorModelObj, connectorCableAnchor, partCableAnchor); }
/// <inheritdoc/> protected override void SetupStateMachine() { base.SetupStateMachine(); linkStateMachine.onAfterTransition += (start, end) => UpdateContextMenu(); linkStateMachine.AddStateHandlers( LinkState.Linked, enterHandler: oldState => { var module = linkTarget as PartModule; PartModuleUtils.InjectEvent(this, DetachConnectorEvent, module); PartModuleUtils.AddEvent(module, GrabConnectorEventInject); }, leaveHandler: newState => { var module = linkTarget as PartModule; PartModuleUtils.WithdrawEvent(this, DetachConnectorEvent, module); PartModuleUtils.DropEvent(module, GrabConnectorEventInject); }); // The default state is "Locked". All the enter state handlers rely on it, and all the exit // state handlers reset the state back to the default. connectorStateMachine = new SimpleStateMachine <ConnectorState>(strict: true); connectorStateMachine.onAfterTransition += (start, end) => { if (end != null) // Do nothing on state machine shutdown. { persistedIsConnectorLocked = isConnectorLocked; if (end == ConnectorState.Locked) { KASAPI.AttachNodesUtils.AddNode(part, coupleNode); } else if (coupleNode.attachedPart == null) { KASAPI.AttachNodesUtils.DropNode(part, coupleNode); } UpdateContextMenu(); } HostedDebugLog.Info(this, "Connector state changed: {0} => {1}", start, end); }; connectorStateMachine.SetTransitionConstraint( ConnectorState.Docked, new[] { ConnectorState.Plugged, ConnectorState.Locked, // External detach. }); connectorStateMachine.SetTransitionConstraint( ConnectorState.Locked, new[] { ConnectorState.Deployed, ConnectorState.Plugged, ConnectorState.Docked, // External attach. }); connectorStateMachine.SetTransitionConstraint( ConnectorState.Deployed, new[] { ConnectorState.Locked, ConnectorState.Plugged, }); connectorStateMachine.SetTransitionConstraint( ConnectorState.Plugged, new[] { ConnectorState.Deployed, ConnectorState.Docked, }); connectorStateMachine.AddStateHandlers( ConnectorState.Locked, enterHandler: oldState => { connectorModelObj.parent = nodeTransform; PartModel.UpdateHighlighters(part); connectorModelObj.GetComponentsInChildren <Renderer>().ToList() .ForEach(r => r.SetPropertyBlock(part.mpb)); AlignTransforms.SnapAlign(connectorModelObj, connectorCableAnchor, partCableAnchor); SetCableLength(0); if (oldState.HasValue) // Skip when restoring state. { UISoundPlayer.instance.Play(sndPathLockConnector); } }, callOnShutdown: false); connectorStateMachine.AddStateHandlers( ConnectorState.Docked, enterHandler: oldState => { connectorModelObj.parent = nodeTransform; AlignTransforms.SnapAlign(connectorModelObj, connectorCableAnchor, partCableAnchor); SetCableLength(0); // Align the docking part to the nodes if it's a separate vessel. if (oldState.HasValue && linkTarget.part.vessel != vessel) { AlignTransforms.SnapAlignNodes(linkTarget.coupleNode, coupleNode); linkJoint.SetCoupleOnLinkMode(true); UISoundPlayer.instance.Play(sndPathDockConnector); } }, leaveHandler: newState => linkJoint.SetCoupleOnLinkMode(false), callOnShutdown: false); connectorStateMachine.AddStateHandlers( ConnectorState.Deployed, enterHandler: oldState => { TurnConnectorPhysics(true); connectorModelObj.parent = connectorModelObj; PartModel.UpdateHighlighters(part); linkRenderer.StartRenderer(partCableAnchor, connectorCableAnchor); }, leaveHandler: newState => { TurnConnectorPhysics(false); linkRenderer.StopRenderer(); }, callOnShutdown: false); connectorStateMachine.AddStateHandlers( ConnectorState.Plugged, enterHandler: oldState => { // Destroy the previous highlighter if any, since it would interfere with the new owner. DestroyImmediate(connectorModelObj.GetComponent <Highlighter>()); connectorModelObj.parent = linkTarget.nodeTransform; PartModel.UpdateHighlighters(part); PartModel.UpdateHighlighters(linkTarget.part); connectorModelObj.GetComponentsInChildren <Renderer>().ToList() .ForEach(r => r.SetPropertyBlock(linkTarget.part.mpb)); AlignTransforms.SnapAlign( connectorModelObj, connectorPartAnchor, linkTarget.nodeTransform); linkRenderer.StartRenderer(partCableAnchor, connectorCableAnchor); }, leaveHandler: newState => { var oldParent = connectorModelObj.GetComponentInParent <Part>(); var oldHigh = oldParent.HighlightActive; if (oldHigh) { // Disable the part highlight to restore the connector's renderer materials. oldParent.SetHighlight(false, false); } connectorModelObj.parent = nodeTransform; // Back to the model. PartModel.UpdateHighlighters(part); PartModel.UpdateHighlighters(oldParent); if (oldHigh) { oldParent.SetHighlight(true, false); } linkRenderer.StopRenderer(); }, callOnShutdown: false); }