/// <summary>Removes items form the stack.</summary> /// <remarks> /// This method does its best. If it cannot remove the desired number of items, then it removes as /// many is there are avaiable in the slot. /// </remarks> /// <param name="qty">The number of items to remove.</param> /// <returns>The actual number of items removed.</returns> public int StackRemove(int qty) { if (qty <= 0) { return(0); } int removeQty; if (quantity - qty < 0) { removeQty = quantity; HostedDebugLog.Fine(inventory, "Exhausted item quantity: name={0}, removeExactly={1}", availablePart.name, removeQty); } else { removeQty = qty; } quantity -= removeQty; inventory.RefreshContents(); if (quantity == 0) { Delete(); } return(removeQty); }
/// <summary> /// Creates a distance joint between the source and an arbitrary physical object. /// </summary> /// <remarks>It sets the maximum cable length to the persisted value. Even if it's zero!</remarks> /// <param name="source">The source of the link.</param> /// <param name="tgtRb">The rigidbody of the physical object.</param> /// <param name="tgtAnchor">The anchor at the physical object in world coordinates.</param> void CreateDistanceJoint(ILinkSource source, Rigidbody tgtRb, Vector3 tgtAnchor) { var distanceLimit = originalLength ?? Vector3.Distance(GetSourcePhysicalAnchor(source), tgtAnchor); var joint = source.part.gameObject.AddComponent <ConfigurableJoint>(); KASAPI.JointUtils.ResetJoint(joint); if (distanceLimit < 0.001f) { // Reset the distance if it's below the KSP distance resolution. HostedDebugLog.Fine(this, "Reset joint to zero: distance={0}", distanceLimit); distanceLimit = 0; } KASAPI.JointUtils.SetupDistanceJoint( joint, springForce: cableSpringForce, springDamper: cableSpringDamper, maxDistance: distanceLimit); joint.autoConfigureConnectedAnchor = false; joint.anchor = source.part.Rigidbody.transform.InverseTransformPoint( GetSourcePhysicalAnchor(source)); joint.connectedBody = tgtRb; joint.connectedAnchor = tgtRb.transform.InverseTransformPoint(tgtAnchor); SetBreakForces(joint); SetCustomJoints(new[] { joint }); cableJoint = joint; }
/// <summary>Couples the source and the target parts merging them into a single vessel.</summary> /// <remarks> /// It's OK to call this method if the parts are already coupled. It's a normal way to have the /// attach nodes created on the vessel load. /// </remarks> /// <seealso cref="DecoupleParts"/> void CoupleParts() { if (isCoupled) { // If the parts are already coupled, then refresh the state and update the joints. if (persistedSrcVesselInfo == null) { HostedDebugLog.Fine(this, "Update link source vessel info to: {0}", vessel); persistedSrcVesselInfo = GetVesselInfo(vessel); } if (persistedTgtVesselInfo == null) { HostedDebugLog.Fine(this, "Update link target vessel info to: {0}", vessel); persistedTgtVesselInfo = GetVesselInfo(vessel); } SetupPhysXJoints(); return; } if (linkSource.part.vessel == linkTarget.part.vessel) { // If the parts belong to the same vessel, but are not coupled, then update the joints. HostedDebugLog.Fine(this, "Already coupled, skipping: {0} <=> {1}", linkSource, linkTarget); SetupPhysXJoints(); return; } // Remember the vessel info to restore it on the decoupling. And do the couple! persistedSrcVesselInfo = GetVesselInfo(linkSource.part.vessel); persistedTgtVesselInfo = GetVesselInfo(linkTarget.part.vessel); KASAPI.LinkUtils.CoupleParts( linkSource.coupleNode, linkTarget.coupleNode, toDominantVessel: true); SetupPhysXJoints(); }
/// <inheritdoc/> protected override void CheckCoupleNode() { base.CheckCoupleNode(); if (linkState == LinkState.Available && parsedAttachNode.attachedPart != null) { var target = parsedAttachNode.attachedPart.Modules .OfType <ILinkTarget>() .FirstOrDefault(t => t.coupleNode != null && t.coupleNode.attachedPart == part && CheckCanLinkTo(t, reportToLog: false)); if (target != null) { HostedDebugLog.Fine(this, "Linking with the preattached part: {0}", target); LinkToTarget(LinkActorType.API, target); } if (!isLinked) { HostedDebugLog.Warning(this, "Cannot link to the preattached part via {0}", KASAPI.AttachNodesUtils.NodeId(parsedAttachNode.FindOpposingNode())); isNodeBlocked = true; } } else if (linkState == LinkState.NodeIsBlocked && parsedAttachNode.attachedPart == null) { isNodeBlocked = false; } // Restore the link state if not yet done. if (isLinked && !linkJoint.isLinked) { linkJoint.CreateJoint(this, linkTarget); } UpdateContextMenu(); // To update the dock/undock menu. }
/// <inheritdoc/> public override void OnLoad(ConfigNode node) { ConfigAccessor.ReadPartConfig(this, cfgNode: node); ConfigAccessor.ReadFieldsFromNode(node, GetType(), this, StdPersistentGroups.PartPersistant); base.OnLoad(node); parsedAttachNode = part.FindAttachNode(attachNodeName); isAutoAttachNode = parsedAttachNode == null; if (isAutoAttachNode) { parsedAttachNode = KASAPI.AttachNodesUtils.ParseNodeFromString( part, attachNodeDef, attachNodeName); if (parsedAttachNode != null) { HostedDebugLog.Fine( this, "Created auto node: {0}", KASAPI.AttachNodesUtils.NodeId(parsedAttachNode)); if (coupleNode != null && (HighLogic.LoadedSceneIsFlight || HighLogic.LoadedSceneIsEditor)) { // Only pre-add the node in the scenes that assume restoring a vessel state. // We'll drop it in the OnStartFinished if not used. KASAPI.AttachNodesUtils.AddNode(part, coupleNode); } } else { HostedDebugLog.Error(this, "Cannot create auto node from: {0}", attachNodeDef); } } if (parsedAttachNode != null) { // HACK: Handle a KIS issue which causes the nodes to be owned by the prefab part. parsedAttachNode.owner = part; nodeTransform = KASAPI.AttachNodesUtils.GetTransformForNode(part, parsedAttachNode); } }
/// <inheritdoc/> public virtual void OnPartUnpack() { // The check may want to establish a link, but this will only succeed if the physics has // started. HostedDebugLog.Fine(this, "Schedule coupling check from UNPACK..."); AsyncCall.CallOnEndOfFrame(this, CheckCoupleNode); }
/// <inheritdoc/> protected override void SetupStateMachine() { base.SetupStateMachine(); linkStateMachine.onAfterTransition += (start, end) => HostedDebugLog.Fine( this, "Target state changed: node={0}, state {1} => {2}", attachNodeName, start, end); linkStateMachine.SetTransitionConstraint( LinkState.Available, new[] { LinkState.AcceptingLinks, LinkState.NodeIsBlocked, LinkState.Locked }); linkStateMachine.SetTransitionConstraint( LinkState.NodeIsBlocked, new[] { LinkState.Available }); linkStateMachine.SetTransitionConstraint( LinkState.AcceptingLinks, new[] { LinkState.Available, LinkState.Linked }); linkStateMachine.SetTransitionConstraint( LinkState.Linked, new[] { LinkState.Available }); linkStateMachine.SetTransitionConstraint( LinkState.Locked, new[] { LinkState.Available }); linkStateMachine.AddStateHandlers( LinkState.Available, enterHandler: x => KASAPI.KasEvents.OnStartLinking.Add(OnStartLinkingKASEvent), leaveHandler: x => KASAPI.KasEvents.OnStartLinking.Remove(OnStartLinkingKASEvent)); linkStateMachine.AddStateHandlers( LinkState.AcceptingLinks, enterHandler: x => KASAPI.KasEvents.OnStopLinking.Add(OnStopLinkingKASEvent), leaveHandler: x => KASAPI.KasEvents.OnStopLinking.Remove(OnStopLinkingKASEvent)); linkStateMachine.AddStateHandlers( LinkState.AcceptingLinks, enterHandler: x => SetEligiblePartHighlighting(true), leaveHandler: x => SetEligiblePartHighlighting(false), callOnShutdown: false); }
/// <inheritdoc/> public virtual bool SetCoupleOnLinkMode(bool isCoupleOnLink) { if (!isLinked) { coupleOnLinkMode = isCoupleOnLink; HostedDebugLog.Fine( this, "Coupling mode updated in a non-linked module: {0}", isCoupleOnLink); return(true); } if (isCoupleOnLink && (linkSource.coupleNode == null || linkTarget.coupleNode == null)) { HostedDebugLog.Error(this, "Cannot couple due to source or target doesn't support it"); coupleOnLinkMode = false; return(false); } if (isCoupleOnLink && linkSource.part.vessel != linkTarget.part.vessel) { // Couple the parts, and drop the other link(s). DetachParts(); coupleOnLinkMode = isCoupleOnLink; CoupleParts(); } else if (!isCoupleOnLink && isCoupled) { // Decouple the parts, and make the non-coupling link(s). DecoupleParts(); coupleOnLinkMode = isCoupleOnLink; AttachParts(); } else { coupleOnLinkMode = isCoupleOnLink; // Simply change the mode. } return(true); }
/// <summary> /// Goes thru the parts on the source and target vessels, and tries to restore the coupling /// between the parts. /// </summary> /// <remarks> /// Any linking module on the source or the target vessel, which is linked and in the docking /// mode, will be attempted to use to restore the vessels coupling. This work will be done at the /// end of frame to let the other logic to cleanup. /// </remarks> /// <param name="tgtPart"> /// The former target part that was holding the coupling with this part. /// </param> void DelegateCouplingRole(Part tgtPart) { AsyncCall.CallOnEndOfFrame(this, () => { var candidates = new List <ILinkJoint>() .Concat(vessel.parts .SelectMany(p => p.Modules.OfType <ILinkJoint>()) .Where(j => !ReferenceEquals(j, this) && j.coupleOnLinkMode && j.isLinked && j.linkTarget.part.vessel == tgtPart.vessel)) .Concat(tgtPart.vessel.parts .SelectMany(p => p.Modules.OfType <ILinkJoint>()) .Where(j => j.coupleOnLinkMode && j.isLinked && j.linkTarget.part.vessel == vessel)); foreach (var joint in candidates) { HostedDebugLog.Fine(this, "Trying to couple via: {0}", joint); if (joint.SetCoupleOnLinkMode(true)) { HostedDebugLog.Info(this, "The coupling role is delegated to: {0}", joint); return; } } if (candidates.Any()) { HostedDebugLog.Warning(this, "None of the found candidates took the coupling role"); } }); }
/// <inheritdoc/> public virtual void DecoupleAction(string nodeName, bool weDecouple) { if (nodeName == attachNodeName) { HostedDebugLog.Fine(this, "Schedule coupling check from DECOUPLE action..."); AsyncCall.CallOnEndOfFrame(this, CheckCoupleNode); } }
/// <summary>Adds a custom part module and loads its fields from the config.</summary> T AddModule <T>(Part prefab, ConfigNode node) where T : PartModule { var module = prefab.AddModule(typeof(T).Name, forceAwake: true) as T; HostedDebugLog.Fine(module, "Add module and load config: type={0}", typeof(T)); module.Fields.Load(node); return(module); }
/// <inheritdoc/> public virtual void CancelLinking() { if (!linkStateMachine.CheckCanSwitchTo(LinkState.Available)) { HostedDebugLog.Fine(this, "Ignore linking mode cancel in state: {0}", linkState); return; } linkState = LinkState.Available; }
/// <summary> /// Creates a stock joint between the coupled parts, given there is none already created. /// </summary> /// <remarks>The created joint (if any) is populated to the hosting part.</remarks> void MaybeCreateStockJoint() { if (linkTarget.part.attachJoint == null) { HostedDebugLog.Fine( this, "Create a stock joint between: {0} <=> {1}", linkSource, linkTarget); linkTarget.part.CreateAttachJoint(AttachModes.STACK); } }
/// <summary>Creates a stock-aloke joint between the unrealted parts.</summary> /// <remarks>The physical joints will be controlled by the module.</remarks> void CreateCustomJoint() { HostedDebugLog.Fine( this, "Create a stock-alike joint between: {0} <=> {1}", linkSource, linkTarget); var stockJoint = PartJoint.Create(linkSource.part, linkTarget.part, linkSource.coupleNode, linkTarget.coupleNode, AttachModes.STACK); SetCustomJoints(stockJoint.joints.ToArray()); }
/// <inheritdoc/> public virtual void CancelLinking() { if (!linkStateMachine.CheckCanSwitchTo(LinkState.Available)) { HostedDebugLog.Fine(this, "Ignore linking mode cancel in state: {0}", linkState); return; } SetLinkState(LinkState.Available); KASAPI.KasEvents.OnStopLinking.Fire(this); }
/// <summary>Drops and cleans up all the PhysX joints between the rigid objects.</summary> /// <remarks> /// <para> /// The default implementation simply destroys the joints from the <see cref="customJoints"/> /// collection. In most cases it's enough to update the physics in the game. However, if module /// manages some other objects or components, then this method is the right place to do the /// cleanup. /// </para> /// <para> /// IMPORTANT! The <see cref="SetCustomJoints"/> method cleans up all the joints by invoking this /// method. If there are extra objects that the child class needs to cleanup, then they must /// <i>not</i> get initialized before the new joints are set. Otherwise, the newly created objects /// may get destroyed. The suggested way of cleaning up the Unity objects is adding them into the /// <see cref="customExtraObjects"/> collection. /// </para> /// </remarks> /// <seealso cref="customJoints"/> /// <seealso cref="customExtraObjects"/> protected virtual void CleanupPhysXJoints() { if (customJoints.Count > 0) { HostedDebugLog.Fine(this, "Drop {0} joint(s) to: {1}", customJoints.Count, linkTarget); customJoints.ForEach(Object.Destroy); customJoints.Clear(); customExtraObjects.ForEach(Object.Destroy); customExtraObjects.Clear(); } }
/// <summary>Calls renderer updates as long as the renderer is started.</summary> /// <seealso cref="linkUpdateCoroutine"/> /// <seealso cref="StartRenderer"/> IEnumerator UpdateLinkCoroutine() { HostedDebugLog.Fine(this, "Staring renderer updates..."); while (isStarted) { UpdateLink(); yield return(null); } // The coroitine is expected to be terminated explicitly! HostedDebugLog.Warning(this, "Terminate coroutine on renderer stop!"); }
/// <summary>Reacts on a part de-coupling and adjusts its colliders as needed.</summary> /// <remarks> /// When a part is leaving the target vessel, the collsions between this part and the pipe meshes /// must be restored. /// </remarks> /// <param name="originator">The part that has decoupled.</param> void OnPartDeCoupleCompleteEvent(Part originator) { if (formerTargetVessel != null && originator.vessel != formerTargetVessel) { // It's either the traget part has decoupled from its vessel, or the owner vessel has // abandoned the target part. var leavingVessel = originator == targetPart ? formerTargetVessel : originator.vessel; HostedDebugLog.Fine(this, "Restore collision ignores on: {0}", leavingVessel); leavingVessel.parts .ForEach(p => SetCollisionIgnores(p, false)); } formerTargetVessel = null; }
/// <inheritdoc cref="IPartModule.OnStart" /> public override void OnStart(StartState state) { base.OnStart(state); if (!_moduleSettingsLoaded) { _moduleSettingsLoaded = true; if (!HighLogic.LoadedSceneIsEditor) { HostedDebugLog.Fine(this, "Late load of module settings. Save file inconsistency?"); } InitModuleSettings(); } }
/// <inheritdoc/> protected override void SetupStateMachine() { base.SetupStateMachine(); linkStateMachine.onAfterTransition += (start, end) => HostedDebugLog.Fine( this, "Source state changed at {0}: {1} => {2}", attachNodeName, start, end); linkStateMachine.SetTransitionConstraint( LinkState.Available, new[] { LinkState.Linking, LinkState.RejectingLinks, LinkState.NodeIsBlocked }); linkStateMachine.SetTransitionConstraint( LinkState.NodeIsBlocked, new[] { LinkState.Available }); linkStateMachine.SetTransitionConstraint( LinkState.Linking, new[] { LinkState.Available, LinkState.Linked }); linkStateMachine.SetTransitionConstraint( LinkState.Linked, new[] { LinkState.Available }); linkStateMachine.SetTransitionConstraint( LinkState.Locked, new[] { LinkState.Available }); linkStateMachine.SetTransitionConstraint( LinkState.RejectingLinks, new[] { LinkState.Available, LinkState.Locked }); linkStateMachine.AddStateHandlers( LinkState.Available, enterHandler: x => KASAPI.KasEvents.OnStartLinking.Add(OnStartLinkingKASEvent), leaveHandler: x => KASAPI.KasEvents.OnStartLinking.Remove(OnStartLinkingKASEvent)); linkStateMachine.AddStateHandlers( LinkState.RejectingLinks, enterHandler: x => KASAPI.KasEvents.OnStopLinking.Add(OnStopLinkingKASEvent), leaveHandler: x => KASAPI.KasEvents.OnStopLinking.Remove(OnStopLinkingKASEvent)); linkStateMachine.AddStateHandlers( LinkState.Linked, enterHandler: x => { GameEvents.onVesselWillDestroy.Add(OnVesselWillDestroyGameEvent); var module = linkTarget as PartModule; PartModuleUtils.InjectEvent(this, ToggleVesselsDockModeEvent, module); }, leaveHandler: x => { GameEvents.onVesselWillDestroy.Remove(OnVesselWillDestroyGameEvent); var module = linkTarget as PartModule; PartModuleUtils.WithdrawEvent(this, ToggleVesselsDockModeEvent, module); }); linkStateMachine.AddStateHandlers( LinkState.Linking, enterHandler: x => KASAPI.KasEvents.OnStartLinking.Fire(this), leaveHandler: x => KASAPI.KasEvents.OnStopLinking.Fire(this)); }
/// <summary>Copies the custom part fields from the prefab into the instance.</summary> /// <remarks> /// The consumer code must call this method from the <c>OnAwake</c> method to ensure the custom /// fields are properly initialized. /// </remarks> /// <param name="tgtModule">The module to copy the fields into.</param> /// <seealso cref="ReadPartConfig"/> /// <example> /// <code source="Examples/ConfigUtils/ConfigAccessor-Examples.cs" region="ReadPartConfigExample"/> /// </example> public static void CopyPartConfigFromPrefab(PartModule tgtModule) { var part = tgtModule.part; if (PartLoader.Instance.IsReady()) { var moduleIdx = part.Modules.IndexOf(tgtModule); if (moduleIdx == -1) { // Modules in the unloaded parts awake before being added into part. moduleIdx = part.Modules.Count; } if (moduleIdx >= part.partInfo.partPrefab.Modules.Count) { HostedDebugLog.Error( tgtModule, "The prefab part doesn't have the module at {0}", moduleIdx); return; } var srcModule = part.partInfo.partPrefab.Modules[moduleIdx]; if (srcModule.moduleName != tgtModule.moduleName) { HostedDebugLog.Error( tgtModule, "Mismatched module in prefab at {0}: expected={1}, found={2}", moduleIdx, tgtModule.moduleName, srcModule.moduleName); if (GameSettings.VERBOSE_DEBUG_LOG) { HostedDebugLog.Fine( tgtModule, "*** DUMP OF AVAILABLE MODULES: infoName={0}, prefabName{1}", part.partInfo.name, part.partInfo.partPrefab.name); for (var i = 0; i < part.partInfo.partPrefab.Modules.Count; i++) { DebugEx.Fine("* name={0}, id=#{1}", part.partInfo.partPrefab.Modules[i].moduleName, i); } } return; } var fields = PersistentFieldsFactory.GetPersistentFields( tgtModule.GetType(), false /* needStatic */, true /* needInstance */, StdPersistentGroups.PartConfigLoadGroup); foreach (var field in fields) { // We need a copy, so get it thru the persistence. var copyNode = new ConfigNode(); field.WriteToConfig(copyNode, srcModule); field.ReadFromConfig(copyNode, tgtModule); } } }
/// <inheritdoc/> public void AddNode(Part part, AttachNode attachNode) { ArgumentGuard.NotNull(part, "part"); ArgumentGuard.NotNull(attachNode, "attachNode", context: part); if (attachNode.owner != part) { HostedDebugLog.Warning( part, "Former owner of the attach node doesn't match the new one: {0}", attachNode.owner); attachNode.owner = part; } if (part.attachNodes.IndexOf(attachNode) == -1) { HostedDebugLog.Fine(part, "Adding node: {0}", NodeId(attachNode)); part.attachNodes.Add(attachNode); } }
/// <inheritdoc/> public void DropNode(Part part, AttachNode attachNode) { ArgumentGuard.NotNull(part, "part"); ArgumentGuard.NotNull(attachNode, "attachNode", context: part); if (attachNode.attachedPart != null) { HostedDebugLog.Error(part, "Not dropping an attached node: {0}", NodeId(attachNode)); return; } if (part.attachNodes.IndexOf(attachNode) != -1) { HostedDebugLog.Fine(part, "Drop attach node: {0}", NodeId(attachNode)); part.attachNodes.Remove(attachNode); attachNode.attachedPartId = 0; // Just in case. } }
/// <summary>Updates the item's resource.</summary> /// <param name="name">The new of the resource.</param> /// <param name="amount">The new amount or delta.</param> /// <param name="isAmountRelative"> /// Tells if the amount must be added to the current item's amount instead of simply replacing it. /// </param> public void UpdateResource(string name, double amount, bool isAmountRelative = false) { var res = KISAPI.PartNodeUtils.UpdateResource(partNode, name, amount, isAmountRelative: isAmountRelative); if (res.HasValue) { HostedDebugLog.Fine( inventory, "Updated item resource: name={0}, newAmount={1}", name, res); inventory.RefreshContents(); } else { HostedDebugLog.Error( inventory, "Cannot find resource {0} in item for {1}", name, availablePart.name); } }
/// <inheritdoc/> protected override void SetupPhysXJoints() { if (isHeadStarted) { HostedDebugLog.Warning(this, "A physical head is running. Stop it before the link!"); StopPhysicalHead(); } CreateDistanceJoint( linkSource, linkTarget.part.Rigidbody, GetTargetPhysicalAnchor(linkSource, linkTarget)); if (partJoint != null && (!allowDockingAtZeroDistance || !Mathf.Approximately(deployedCableLength, 0))) { HostedDebugLog.Fine(this, "Dropping the stock joint to: {0}", partJoint.Child); partJoint.DestroyJoint(); partJoint.Child.attachJoint = null; } }
/// <inheritdoc/> public void SetLockedOnCouple(bool mode) { if (isLinked) { if (isLockedWhenCoupled != mode) { isLockedWhenCoupled = mode; HostedDebugLog.Fine(this, "Change locked on coupled part: {0}", isLockedWhenCoupled); CleanupPhysXJoints(); SetupPhysXJoints(); } } else { isLockedWhenCoupled = mode; HostedDebugLog.Fine(this, "Set locked on couple mode in a non-linked module: {0}", mode); } }
/// <summary>Triggers coupled node check.</summary> void OnPartCoupleEvent(GameEvents.FromToAction <Part, Part> action) { AttachNode node = null; if (action.from == part) { node = action.from.FindPartThroughNodes(action.to); } else if (action.to == part) { node = action.to.FindPartThroughNodes(action.from); } if (node != null && node.id == attachNodeName) { HostedDebugLog.Fine(this, "Schedule coupling check on coupling event: from={0}, to={1}", action.from, action.to); AsyncCall.CallOnEndOfFrame(this, CheckCoupleNode); } }
/// <inheritdoc/> public override void OnStartFinished(PartModule.StartState state) { base.OnStartFinished(state); // Prevent the third-party logic on the auto node. See OnLoad. if ((HighLogic.LoadedSceneIsFlight || HighLogic.LoadedSceneIsEditor) && isAutoAttachNode && coupleNode != null && coupleNode.attachedPart == null) { KASAPI.AttachNodesUtils.DropNode(part, coupleNode); } if (persistedLinkState == LinkState.Linked) { RestoreOtherPeer(); if (otherPeer == null) { HostedDebugLog.Error(this, "Cannot restore the link's peer"); persistedLinkState = LinkState.Available; if (coupleNode != null && coupleNode.attachedPart != null) { // Decouple the coupled part if the link cannot be restored. It'll allow the player to // restore the game status without hacking the save files. AsyncCall.CallOnEndOfFrame( this, () => { if (coupleNode.attachedPart != null) // The part may get decoupled already. { KASAPI.LinkUtils.DecoupleParts(part, coupleNode.attachedPart); } }); } // This may break a lot of logic, built on top of the KAS basic modules. However, it will // allow the main logic to work. Given it's a fallback, it's OK. part.Modules.OfType <AbstractLinkPeer>() .Where(p => p.isLinked && (p.cfgAttachNodeName == attachNodeName || p.cfgDependentNodeNames.Contains(attachNodeName))) .ToList() .ForEach(m => SetLinkState(LinkState.Available)); } else { HostedDebugLog.Fine(this, "Restored link to: {0}", otherPeer); } } SetLinkState(persistedLinkState); }
/// <inheritdoc/> public virtual void StopRenderer() { // Stop meshes updates. if (linkUpdateCoroutine != null) { HostedDebugLog.Fine(this, "Stopping renderer updates..."); StopCoroutine(linkUpdateCoroutine); linkUpdateCoroutine = null; } // Sync the renderers settinsg to the source part to handle the highlights. if (isStarted) { sourceTransform.GetComponentsInChildren <Renderer>().ToList() .ForEach(r => r.SetPropertyBlock(part.mpb)); targetTransform.GetComponentsInChildren <Renderer>().ToList() .ForEach(r => r.SetPropertyBlock(part.mpb)); } DestroyPipeMesh(); PartModel.UpdateHighlighters(part); // Update the target vessel relations (if any). if (targetPart != null) { PartModel.UpdateHighlighters(targetPart); if (targetPart.vessel != vessel) { targetPart.vessel.parts .Where(p => p != null) // It's a cleanup method. .ToList() .ForEach(p => SetCollisionIgnores(p, false)); } } targetPart = null; sourceTransform = null; targetTransform = null; GameEvents.onPartCoupleComplete.Remove(OnPartCoupleCompleteEvent); GameEvents.onPartDeCouple.Remove(OnPartDeCoupleEvent); GameEvents.onPartDeCoupleComplete.Remove(OnPartDeCoupleCompleteEvent); }
/// <inheritdoc/> public virtual bool CreateJoint(ILinkSource source, ILinkTarget target) { if (isLinked) { HostedDebugLog.Error( this, "Cannot link the joint which is already linked to: {0}", linkTarget); return(false); } if (!CheckCoupled(source, target)) { var errors = CheckConstraints(source, target); if (errors.Length > 0) { HostedDebugLog.Error(this, "Cannot create joint:\n{0}", DbgFormatter.C2S(errors)); return(false); } } else { HostedDebugLog.Fine(this, "The parts are coupled. Skip the constraints check"); } linkSource = source; linkTarget = target; if (!originalLength.HasValue) { SetOrigianlLength(Vector3.Distance( GetSourcePhysicalAnchor(source), GetTargetPhysicalAnchor(source, target))); } isLinked = true; // If the parts are already coupled at this moment, then the mode must be set as such. coupleOnLinkMode |= isCoupled; // Ensure the coupling can be done. coupleOnLinkMode &= linkSource.coupleNode != null && linkTarget.coupleNode != null; if (coupleOnLinkMode) { CoupleParts(); } else { AttachParts(); } return(true); }