Exemple #1
0
        /// <summary>Cleans up the internal cache of the injected menu items.</summary>
        /// <param name="menuOwnerPart">The part for which the UI is being destroyed.</param>
        /// <seealso cref="OnPartGUIStart"/>
        void OnPartGUIStop(Part menuOwnerPart)
        {
            List <InjectedEvent> injects;

            if (_targetCandidates.TryGetValue(menuOwnerPart.flightID, out injects))
            {
                injects
                .Where(ie => ie.baseEvent != null).ToList()
                .ForEach(ie => PartModuleUtils.DropEvent(ie.module as PartModule, ie.baseEvent));
                _targetCandidates.Remove(menuOwnerPart.flightID);
            }
        }
Exemple #2
0
        /// <summary>Updates the GUI items when a part's context menu is opened.</summary>
        /// <remarks>
        /// <para>
        /// The goal of this method is to intercept the action of opening a context menu on the other
        /// part. The method checks if the target part can be a target for the link of the connector that
        /// is being carried by the kerbal. If this is the case, then a special menu item is injected to
        /// allow player to complete the link.
        /// </para>
        /// <para>
        /// This event is called once for every part with an opened menu in every frame. For this reason
        /// it must be very efficient, or else the performance will suffer. To not impact the performance,
        /// this method caches all the opened menus.
        /// </para>
        /// </remarks>
        /// <param name="menuOwnerPart">The part for which the UI is created.</param>
        /// <seealso cref="OnPartGUIStop"/>
        void OnPartGUIStart(Part menuOwnerPart)
        {
            if (FlightGlobals.ActiveVessel != vessel)
            {
                // If the EVA part has lost the focus, then cleanup all the caches.
                if (_targetCandidates.Count > 0)
                {
                    _targetCandidates
                    .SelectMany(t => t.Value)
                    .Where(ie => ie.baseEvent != null)
                    .ToList()
                    .ForEach(ie => PartModuleUtils.DropEvent(ie.module as PartModule, ie.baseEvent));
                    _targetCandidates.Clear();
                }
                return;
            }

            // Check if the menu injects need to be added/removed on the monitored parts.
            List <InjectedEvent> injects;

            if (!_targetCandidates.TryGetValue(menuOwnerPart.flightID, out injects))
            {
                injects = menuOwnerPart.Modules.OfType <ILinkTarget>()
                          .Where(t => t.cfgLinkType == cfgLinkType)
                          .Select(t => new InjectedEvent()
                {
                    module = t, baseEvent = null
                })
                          .ToList();
                _targetCandidates.Add(menuOwnerPart.flightID, injects);
            }
            foreach (var inject in injects)
            {
                var target  = inject.module;
                var canLink = inject.module.linkState == LinkState.Available && isLinked;
                if (!canLink && inject.baseEvent != null)
                {
                    PartModuleUtils.DropEvent(target as PartModule, inject.baseEvent);
                    inject.baseEvent = null;
                }
                else if (canLink && inject.baseEvent == null)
                {
                    inject.baseEvent = MakeEvent(target, AttachConnectorMenu, LinkCarriedConnector);
                    PartModuleUtils.AddEvent(target as PartModule, inject.baseEvent);
                }
            }
        }
Exemple #3
0
        /// <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);
        }
Exemple #4
0
        /// <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);
        }