/// <inheritdoc/> protected override void CheckSettingsConsistency() { base.CheckSettingsConsistency(); if (!allowCoupling && coupleMode == CoupleMode.AlwaysCoupled) { allowCoupling = true; HostedDebugLog.Warning( this, "Inconsistent setting fixed: allowCoupling => true, due to coupleMode={0}", coupleMode); } if (!allowCoupling && linkJoint != null && linkJoint.coupleOnLinkMode) { // This check is needed for debug only. linkJoint.SetCoupleOnLinkMode(false); HostedDebugLog.Warning( this, "Inconsistent setting fixed: coupleOnLinkMode => false, due to allowCoupling={0}", allowCoupling); } }
/// <inheritdoc/> protected override void SetupPhysXJoints() { if (isHeadStarted) { HostedDebugLog.Warning(this, "A physical head is running. Stop it before the link!"); StopPhysicalHead(); } var needStockJoint = isCoupled && isLockedWhenCoupled; if (needStockJoint && partJoint == null) { if (linkTarget.part.parent == linkSource.part) { HostedDebugLog.Fine(this, "Create a stock joint: from={0}, to={1}", linkTarget, linkSource); linkTarget.part.CreateAttachJoint(AttachModes.STACK); } else if (linkSource.part.parent == linkTarget.part) { HostedDebugLog.Fine(this, "Create a stock joint: from={0}, to={1}", linkSource, linkTarget); linkSource.part.CreateAttachJoint(AttachModes.STACK); } else { HostedDebugLog.Error( this, "Cannot create stock joint: {0} <=> {1}", linkSource, linkTarget); needStockJoint = false; } } else if (!needStockJoint && partJoint != null) { HostedDebugLog.Fine( this, "Drop stock joint: to={0}, isLockedWhenDocked={1}, isCoupled={2}", partJoint.Child, isLockedWhenCoupled, isCoupled); partJoint.DestroyJoint(); partJoint.Child.attachJoint = null; } if (!needStockJoint) { CreateDistanceJoint( linkSource, linkTarget.part.Rigidbody, GetTargetPhysicalAnchor(linkSource, linkTarget)); } }
/// <inheritdoc/> public Transform GetTransformForNode(Part ownerPart, AttachNode an) { ArgumentGuard.NotNull(ownerPart, "ownerPart"); ArgumentGuard.NotNull(an, "an", context: ownerPart); if (an.owner != ownerPart) { HostedDebugLog.Warning( ownerPart, "Attach node doesn't belong to the part: {0}", NodeId(an)); } var partModel = Hierarchy.GetPartModelTransform(ownerPart); var objectName = "attachNode-" + an.id; var nodeTransform = partModel.Find(objectName) ?? new GameObject(objectName).transform; Hierarchy.MoveToParent( nodeTransform, partModel, newPosition: an.position / ownerPart.rescaleFactor, newRotation: Quaternion.LookRotation(an.orientation)); return(nodeTransform); }
/// <summary>Gets the texture from either a KSP gamebase or the internal cache.</summary> /// <remarks> /// It's OK to call this method in the performance demanding code since once the texture is /// successfully returned it's cached internally. The subsequent calls won't issue expensive game /// database requests. /// </remarks> /// <param name="textureFileName"> /// Filename of the texture file. The path is realtive to <c>GameData</c> folder. The name must /// not have the file extension. /// </param> /// <param name="asNormalMap">If <c>true</c> then the texture will be loaded as a bumpmap.</param> /// <returns> /// The texture. Note that it's a shared object. Don't execute actions on it which you don't want /// to affect the other meshes in the game. /// </returns> /// <seealso href="https://docs.unity3d.com/ScriptReference/Texture2D.html"> /// Unity3D: Texture2D</seealso> protected Texture2D GetTexture(string textureFileName, bool asNormalMap = false) { Texture2D texture; if (!textures.TryGetValue(textureFileName, out texture)) { texture = GameDatabase.Instance.GetTexture(textureFileName, asNormalMap); if (texture == null) { // Use a "red" texture if no file found. HostedDebugLog.Warning(this, "Cannot load texture: {0}", textureFileName); texture = new Texture2D(1, 1, TextureFormat.ARGB32, false); texture.SetPixels(new[] { Color.red }); texture.Apply(); texture.Compress(highQuality: false); } textures[textureFileName] = texture; } return(texture); }
/// <inheritdoc/> public override void OnDebugAdjustablesUpdated() { base.OnDebugAdjustablesUpdated(); AsyncCall.CallOnEndOfFrame( this, () => { HostedDebugLog.Warning(this, "Reloading settings..."); InitModuleSettings(); InitStartState(); UpdateContextMenu(); if (_dbgOldTarget != null) { HostedDebugLog.Warning(this, "Relinking to target: {0}", _dbgOldTarget); LinkToTarget(LinkActorType.Player, _dbgOldTarget); var cableJoint = linkJoint as ILinkCableJoint; if (cableJoint != null) { HostedDebugLog.Warning(this, "Restoring cable length: {0}", _dbgOldCableLength); cableJoint.SetCableLength(_dbgOldCableLength); } } }, skipFrames: 1); // The link's logic is asynchronous. }
/// <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); }
void Destroy() { // The logging below will will identify the game object name by it's full hierarchy path. HostedDebugLog.Warning(part.transform.Find("model"), "Part's model is being destroyed"); }
/// <inheritdoc/> protected override void CheckCoupleNode() { base.CheckCoupleNode(); if (coupleNode == null) { // If the part doesn't want to couple, then we should obey. if (parsedAttachNode.attachedPart != null) { HostedDebugLog.Error( this, "Cannot maintain coupling with: {0}", parsedAttachNode.attachedPart); if (linkState == LinkState.Available) { AsyncCall.CallOnEndOfFrame(this, () => { if (parsedAttachNode.attachedPart) { HostedDebugLog.Info( this, "Decoupling incompatible part: {0}", parsedAttachNode.attachedPart); parsedAttachNode.attachedPart.decouple(); } }); } else if (linkState == LinkState.Linked && linkTarget != null) { HostedDebugLog.Warning(this, "Breaking the link to: {0}", linkTarget); AsyncCall.CallOnEndOfFrame(this, () => BreakCurrentLink(LinkActorType.API)); } else { AsyncCall.CallOnEndOfFrame(this, () => { if (parsedAttachNode.attachedPart) { HostedDebugLog.Error( this, "Cannot pickup coupling in unexpected link state: {0}", linkState); parsedAttachNode.attachedPart.decouple(); } }); } } return; } if (linkState == LinkState.Available && coupleNode != null && coupleNode.attachedPart != null) { var target = coupleNode.attachedPart.Modules .OfType <ILinkTarget>() .FirstOrDefault(t => t.cfgLinkType == cfgLinkType && t.linkState == LinkState.Available && t.coupleNode != null && t.coupleNode.attachedPart == part && CheckCanLinkTo(t)); if (target != null) { HostedDebugLog.Fine(this, "Linking with the pre-attached part: {0}", target); LinkToTarget(LinkActorType.API, target); } if (!isLinked) { // Let the other part a chance to couple, and block if it didn't succeed. HostedDebugLog.Fine(this, "Cannot link, wait for the other part: target={0}", coupleNode.attachedPart); AsyncCall.CallOnEndOfFrame(this, () => { if (linkState == LinkState.Available && coupleNode.attachedPart != null) { HostedDebugLog.Warning(this, "Cannot link to the pre-attached part: from={0}, to={1}", KASAPI.AttachNodesUtils.NodeId(coupleNode), KASAPI.AttachNodesUtils.NodeId(coupleNode.FindOpposingNode())); SetLinkState(LinkState.NodeIsBlocked); } }); } } else if (linkState == LinkState.NodeIsBlocked && parsedAttachNode.attachedPart == null) { SetLinkState(LinkState.Available); } // Restore the link state if not yet done. if (isLinked && !linkJoint.isLinked) { linkJoint.CreateJoint(this, linkTarget); } UpdateContextMenu(); // To update the dock/undock menu. }
/// <summary>Updates or creates the collider.</summary> void UpdateOrCreateCollider() { var meshTransform = Hierarchy.FindPartModelByPath(part, meshPath); if (meshTransform == null) { HostedDebugLog.Error(this, "Cannot find mesh for path: {0}", meshPath); return; } var meshObj = meshTransform.gameObject; // Add or update the custom colldier. if (meshCollider) { UnityEngine.Object.DestroyImmediate(meshObj.GetComponent <Collider>()); var collider = meshObj.AddComponent <MeshCollider>(); collider.convex = true; HostedDebugLog.Info(this, "Added a mesh collider at {0}", collider.transform); } else { var meshBounds = default(Bounds); foreach (var filter in meshObj.GetComponents <MeshFilter>()) { meshBounds.Encapsulate(filter.sharedMesh.bounds); } HostedDebugLog.Fine(this, "Mesh bounds: {0}", meshBounds); if (meshBounds.extents.magnitude < float.Epsilon) { HostedDebugLog.Warning( this, "The mesh bounds are zero, not adding any collider: {0}", meshTransform); return; } // Add collider basing on the requested type. if (primitiveShape == PrimitiveType.Cube) { UnityEngine.Object.DestroyImmediate(meshObj.GetComponent <Collider>()); var collider = meshObj.AddComponent <BoxCollider>(); collider.center = meshBounds.center; collider.size = meshBounds.size; HostedDebugLog.Info(this, "Added a cube collider at {0}: center={1}, size={2}", meshTransform, collider.center, collider.size); } else if (primitiveShape == PrimitiveType.Capsule) { UnityEngine.Object.DestroyImmediate(meshObj.GetComponent <Collider>()); // TODO(ihsoft): Choose direction so that the volume is minimized. var collider = meshObj.AddComponent <CapsuleCollider>(); collider.center = meshBounds.center; collider.direction = 2; // Z axis collider.height = meshBounds.size.z; collider.radius = Mathf.Min(meshBounds.extents.x, meshBounds.extents.y); HostedDebugLog.Info( this, "Added a capsule collider at {0}: center={1}, height={2}, radius={3}", meshTransform, collider.center, collider.height, collider.radius); } else if (primitiveShape == PrimitiveType.Sphere) { UnityEngine.Object.DestroyImmediate(meshObj.GetComponent <Collider>()); var collider = meshObj.AddComponent <SphereCollider>(); collider.center = meshBounds.center; collider.radius = Mathf.Min( meshBounds.extents.x, meshBounds.extents.y, meshBounds.extents.z); HostedDebugLog.Info( this, "Added a spehere collider at {0}: center={1}, radius={2}", meshTransform, collider.center, collider.radius); } else { DebugEx.Error("Unsupported collider: {0}. Ignoring", primitiveShape); } } }