/// <inheritdoc/> public virtual bool LinkToTarget(ILinkTarget target) { if (!linkStateMachine.CheckCanSwitchTo(LinkState.Linked)) { if (linkActor == LinkActorType.Player) { ShowStatusMessage(SourceIsNotAvailableForLinkMsg, isError: true); } HostedDebugLog.Error(this, "Cannot link in state: {0}", linkState); return(false); } if (!CheckCanLinkTo(target, reportToGui: linkActor == LinkActorType.Player)) { return(false); } if (coupleMode == CoupleMode.AlwaysCoupled || coupleNode != null && coupleNode.attachedPart != null) { linkJoint.SetCoupleOnLinkMode(true); } else if (coupleMode == CoupleMode.NeverCouple) { linkJoint.SetCoupleOnLinkMode(false); } LogicalLink(target); PhysicalLink(); return(true); }
/// <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); }
/// <inheritdoc/> public override void OnLoad(ConfigNode node) { // The locked connector with a part attached is get docked. So we require docking mode here. // TODO(ihsoft): Allow non-docking mode. if (!allowCoupling) { HostedDebugLog.Error(this, "The coupling must be allowed for this part to work. Overriding" + " allowCoupling settings to true."); allowCoupling = true; // A bad approach, but better than not having the attach node. } base.OnLoad(node); if (connectorMass > part.mass) { HostedDebugLog.Error(this, "Connector mass is greater than the part's mass: {0} > {1}", connectorMass, part.mass); connectorMass = 0.1f * part.mass; // A fail safe value. } LoadOrCreateConnectorModel(); if (!persistedIsConnectorLocked) { // In case of the connector is not locked to either the winch or the target part, adjust its // model position and rotation. The rest of the state will be restored in the state machine. if (persistedConnectorPosAndRot != null) { var world = gameObject.transform.TransformPosAndRot(persistedConnectorPosAndRot); connectorModelObj.position = world.pos; connectorModelObj.rotation = world.rot; } } }
/// <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/> protected override void CheckSettingsConsistency() { if (!allowCoupling) { // Connector docking mode is required! // TODO(ihsoft): Allow non-docking mode. allowCoupling = true; HostedDebugLog.Warning( this, "Inconsistent setting fixed: allowCoupling => {0}, due to it's required by physical" + " source", allowCoupling); } base.CheckSettingsConsistency(); if (connectorMass > part.mass) { connectorMass = 0.1f * part.mass; // A fail safe value. HostedDebugLog.Warning( this, "Inconsistent setting fixed: connectorMass => {0}, due to partMass={1}", connectorMass, part.mass); } if (linkJoint != null && cableJoint == null) { HostedDebugLog.Error( this, "Cannot fix inconsistent setting: jointName={0} is not cable joint", jointName); } }
/// <summary>Sets up a sound FX group with an audio clip .</summary> /// <param name="obj">The game object to attach sound to.</param> /// <param name="sndPath">The URL to the audio clip.</param> /// <param name="loop">Specifies if the clip playback shold be looped.</param> /// <param name="maxDistance">The maximum distance at which the sound is hearable.</param> /// <returns>An audio source object attached to the <paramref name="obj"/>.</returns> public static AudioSource Create3dSound(GameObject obj, string sndPath, bool loop = false, float maxDistance = 30f) { if (HighLogic.LoadedScene == GameScenes.LOADING || HighLogic.LoadedScene == GameScenes.LOADINGBUFFER) { // Resources are not avaialble during game load. return(null); } if (!GameDatabase.Instance.ExistsAudioClip(sndPath)) { HostedDebugLog.Error(obj.transform, "Sound file not found: {0}", sndPath); } var audio = obj.AddComponent <AudioSource>(); audio.volume = GameSettings.SHIP_VOLUME; audio.rolloffMode = AudioRolloffMode.Linear; audio.dopplerLevel = 0f; audio.spatialBlend = 1f; // Set as 2D audiosource audio.maxDistance = maxDistance; audio.loop = loop; audio.playOnAwake = false; audio.clip = GameDatabase.Instance.GetAudioClip(sndPath); return(audio); }
/// <inheritdoc/> protected override void InitModuleSettings() { base.InitModuleSettings(); _attachBoneTransform = Hierarchy.FindTransformByPath(part.transform, equipBoneName); if (_attachBoneTransform == null) { HostedDebugLog.Error(this, "Cannot find bone for: {0}", equipBoneName); } }
/// <inheritdoc/> public virtual void BreakCurrentLink(LinkActorType actorType) { if (!isLinked) { HostedDebugLog.Error(this, "Cannot break link in state: {0}", linkState); return; } PhysicalUnlink(); LogicalUnlink(actorType); }
/// <summary> /// Finds and returns the requested child model, or the main model if the child is not found. /// </summary> /// <param name="name">Name of the child object to find.</param> /// <returns>Object or node's model itself if the child is not found.</returns> protected Transform GetTransformByName(string name) { var res = model.Find(name); if (res == null) { HostedDebugLog.Error(model, "Cannot find transform: {0}", name); res = model; // Fallback. } return(res); }
/// <summary>Applies a setup function on a KSP part module field.</summary> /// <param name="fieldName">The fields name.</param> /// <param name="setupFn">The function to apply to the field if the one is found.</param> protected void SetupField(string fieldName, Action <BaseField> setupFn) { var kspField = Fields[fieldName]; if (kspField == null) { HostedDebugLog.Error(this, "Cannot find field: {0}", fieldName); return; } setupFn.Invoke(kspField); }
/// <summary>Applies a setup function on a KSP part module event.</summary> /// <param name="eventFn">The event's method signature.</param> /// <param name="setupFn">The function to apply to the event if the one is found.</param> protected void SetupEvent(Action eventFn, Action <BaseEvent> setupFn) { var moduleEvent = Events[eventFn.Method.Name]; if (moduleEvent == null) { HostedDebugLog.Error(this, "Cannot find event: {0}", eventFn.Method.Name); return; } setupFn.Invoke(moduleEvent); }
/// <summary>Finds model by path or logs&throws.</summary> /// <remarks>Just a convenience method to avoid unclear NREs.</remarks> /// <returns>The model. It's never <c>null</c>.</returns> /// <exception cref="ArgumentException">If model cannot be retrieved.</exception> protected Transform FindModelOrThrow(Transform root, string path) { var res = Hierarchy.FindTransformByPath(root, path); if (res == null) { HostedDebugLog.Error(this, "Cannot find model: path={0}, parent={1}", path, root); throw new ArgumentException("Model not found: " + path); } return(res); }
/// <summary>Returns an anchor for the physical joint at the target part.</summary> /// <remarks> /// The anchor will be calculated in the source's part scale, and the target's model scale will /// be ignored. /// </remarks> /// <param name="source">The source of the link.</param> /// <param name="target">The target of the link.</param> /// <returns>The position in the world coordinates.</returns> protected Vector3 GetTargetPhysicalAnchor(ILinkSource source, ILinkTarget target) { var scale = source.nodeTransform.lossyScale; if (Mathf.Abs(scale.x - scale.y) > 1e-05 || Mathf.Abs(scale.x - scale.z) > 1e-05) { HostedDebugLog.Error(this, "Uneven scale on the source part is not supported: {0}", DbgFormatter.Vector(scale)); } return(target.nodeTransform.position + target.nodeTransform.rotation * (anchorAtTarget * scale.x)); }
/// <summary>Applies a setup function on a KSP part module action.</summary> /// <param name="partModule">The module to find the action in.</param> /// <param name="actionFn">The actions's method signature.</param> /// <param name="setupFn">The function to apply to the action if the one is found.</param> /// <returns> /// <c>true</c> if the action was found and the function was applied, <c>false</c> otherwise. /// </returns> /// <seealso cref="GetAction"/> public static bool SetupAction( PartModule partModule, Action <KSPActionParam> actionFn, Action <BaseAction> setupFn) { var moduleEvent = partModule.Actions[actionFn.Method.Name]; if (moduleEvent == null) { HostedDebugLog.Error(partModule, "Cannot find action: {0}", actionFn.Method.Name); return(false); } setupFn.Invoke(moduleEvent); return(true); }
/// <summary>Applies a setup function on a KSP part module event.</summary> /// <param name="partModule">The module to find the event in.</param> /// <param name="eventFn">The event's method signature.</param> /// <param name="setupFn">The function to apply to the event if the one is found.</param> /// <returns> /// <c>true</c> if the event was found and the function was applied, <c>false</c> otherwise. /// </returns> /// <seealso cref="GetEvent"/> /// <example><code source="Examples/PartUtils/PartModuleUtils-Examples.cs" region="PartModuleUtils_SetupEvent"/></example> public static bool SetupEvent( PartModule partModule, Action eventFn, Action <BaseEvent> setupFn) { var moduleEvent = partModule.Events[eventFn.Method.Name]; if (moduleEvent == null) { HostedDebugLog.Error(partModule, "Cannot find event: {0}", eventFn.Method.Name); return(false); } setupFn.Invoke(moduleEvent); return(true); }
/// <inheritdoc/> public override void OnLoad(ConfigNode node) { base.OnLoad(node); if (animationState) { UpdateAnimationState(); } else { HostedDebugLog.Error( this, "Bad model or config in part {0}. Cannot find animation: {1}", part, animationName); } }
/// <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 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. } }
/// <inheritdoc/> public virtual void StartPhysicalHead(ILinkSource source, Transform headObjAnchor) { headRb = headObjAnchor.GetComponentInParent <Rigidbody>(); if (isHeadStarted || isLinked || headRb == null) { HostedDebugLog.Error(this, "Bad link state for the physical head start: isLinked={0}, isHeadStarted={1}, hasRb={2}", isLinked, isHeadStarted, headRb != null); return; } headSource = source; // Attach the head to the source. CreateDistanceJoint(source, headRb, headObjAnchor.position); SetOriginalLength(deployedCableLength); }
/// <inheritdoc/> public override void OnStart(PartModule.StartState state) { base.OnStart(state); linkJoint = part.Modules.OfType <ILinkJoint>() .FirstOrDefault(x => x.cfgJointName == jointName); if (linkJoint == null) { HostedDebugLog.Error(this, "KAS part misses a joint module. It won't work properly"); } linkRenderer = part.Modules.OfType <ILinkRenderer>() .FirstOrDefault(x => x.cfgRendererName == linkRendererName); if (linkRenderer == null) { HostedDebugLog.Error(this, "KAS part misses a renderer module. It won't work properly"); } }
/// <summary> /// Decouples the source and the target parts turning them into the separate vessels. /// </summary> /// <seealso cref="CoupleParts"/> void DecoupleParts() { if (!isCoupled) { HostedDebugLog.Error(this, "Cannot decouple - bad link/part state"); return; } selfDecoupledAction = true; // Protect the action to not let the link auto-broken. KASAPI.LinkUtils.DecoupleParts( linkSource.part, linkTarget.part, vesselInfo1: persistedSrcVesselInfo, vesselInfo2: persistedTgtVesselInfo); selfDecoupledAction = false; persistedSrcVesselInfo = null; persistedTgtVesselInfo = null; DelegateCouplingRole(linkTarget.part); SetCustomJoints(null); }
/// <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); } }
/// <summary>Get the module's model shader.</summary> /// <remarks>Implements a fallback logic to not crash if the shader is not found.</remarks> /// <param name="overrideShaderName"> /// The alternative name of the shader to prefer. If set, then the module's shader name is ignored /// in favor of the one provided. /// </param> /// <returns>The requested shader, or a fallback share. It's never <c>null</c>.</returns> protected Shader GetShader(string overrideShaderName = null) { var shaderToFind = overrideShaderName ?? shaderName; var shader = Shader.Find(shaderToFind); if (shader == null) { // Fallback if the shader cannot be found. HostedDebugLog.Error(this, "Cannot find shader: {0}. Using default.", shaderToFind); shader = Shader.Find(FallbackShaderName); Preconditions.NotNull( shader, message: "Failed to create a fallback shader: " + FallbackShaderName, context: this); } return(shader); }
/// <inheritdoc/> public virtual void StartPhysicalHead(ILinkSource source, Transform headObjAnchor) { //FIXME: add the physical head module here. headRb = headObjAnchor.GetComponentInParent <Rigidbody>(); if (isHeadStarted || isLinked || headRb == null) { HostedDebugLog.Error(this, "Bad link state for the physical head start: isLinked={0}, isHeadStarted={1}, hasRb=[2}", isLinked, isHeadStarted, headRb != null); return; } headSource = source; headPhysicalAnchor = headObjAnchor; // Attach the head to the source. CreateDistanceJoint(source, headRb, headObjAnchor.position); }
/// <inheritdoc/> public override void OnLoad(ConfigNode node) { base.OnLoad(node); if (part.Resources.Count == 0) { HostedDebugLog.Error(this, "No resources on the canister! This won't work."); return; } if (part.Resources.Count > 1) { HostedDebugLog.Error(this, "Too many resources on the canister! The first one will be used."); } _mainResourceName = part.Resources[0].resourceName; if (_mainResourceName != StockResourceNames.EvaPropellant) { HostedDebugLog.Info(this, "Using a customized resource type: {0}", _mainResourceName); } }
/// <inheritdoc/> public virtual bool LinkToTarget(ILinkTarget target) { if (!linkStateMachine.CheckCanSwitchTo(LinkState.Linked)) { if (linkActor == LinkActorType.Player) { ShowStatusMessage(SourceIsNotAvailableForLinkMsg, isError: true); } HostedDebugLog.Error(this, "Cannot link in state: {0}", linkState); return(false); } if (!CheckCanLinkTo(target, reportToGUI: linkActor == LinkActorType.Player)) { return(false); } LogicalLink(target); PhysicaLink(); return(true); }
/// <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 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); }
/// <inheritdoc/> public string[] CheckColliderHits(Transform source, Transform target) { if (!pipeColliderIsPhysical) { return(new string[0]); // No need to check, the meshes will never collide. } // HACK: Start the renderer before getting the pipes. var oldStartState = isStarted; var oldPhyscalState = isPhysicalCollider; if (!isStarted) { isPhysicalCollider = false; StartRenderer(source, target); } else if (sourceTransform != source || targetTransform != target) { HostedDebugLog.Error(this, "Cannot verify hits on a started renderer"); } var points = GetPipePath(source, target); if (!oldStartState) { StopRenderer(); isPhysicalCollider = oldPhyscalState; } var hitParts = new HashSet <Part>(); for (var i = 0; i < points.Length - 1; i++) { CheckHitsForCapsule(points[i + 1], points[i], pipeDiameter, target, hitParts); } var hitMessages = new List <string>(); foreach (var hitPart in hitParts) { hitMessages.Add(hitPart != null ? LinkCollidesWithObjectMsg.Format(hitPart) : LinkCollidesWithSurfaceMsg.Format()); } return(hitMessages.ToArray()); }
/// <inheritdoc/> public AttachNode ParseNodeFromString(Part ownerPart, string def, string nodeId) { ArgumentGuard.NotNull(ownerPart, "ownerPart"); ArgumentGuard.NotNullOrEmpty(def, "def", context: ownerPart); ArgumentGuard.NotNullOrEmpty(nodeId, "nodeId", context: ownerPart); var array = def.Split(','); ArgumentGuard.InRange(array.Length, "def", 6, 10, message: "Unexpected number of components", context: ownerPart); try { // The logic is borrowed from PartLoader.ParsePart. var attachNode = new AttachNode { owner = ownerPart, id = nodeId }; var factor = ownerPart.rescaleFactor; attachNode.position = new Vector3( float.Parse(array[0]), float.Parse(array[1]), float.Parse(array[2])) * factor; attachNode.orientation = new Vector3( float.Parse(array[3]), float.Parse(array[4]), float.Parse(array[5])) * factor; attachNode.originalPosition = attachNode.position; attachNode.originalOrientation = attachNode.orientation; attachNode.size = array.Length >= 7 ? int.Parse(array[6]) : 1; attachNode.attachMethod = array.Length >= 8 ? (AttachNodeMethod)int.Parse(array[7]) : AttachNodeMethod.FIXED_JOINT; if (array.Length >= 9) { attachNode.ResourceXFeed = int.Parse(array[8]) > 0; } if (array.Length >= 10) { attachNode.rigid = int.Parse(array[9]) > 0; } attachNode.nodeType = AttachNode.NodeType.Stack; return(attachNode); } catch (Exception ex) { HostedDebugLog.Error(ownerPart, "Cannot parse node '{0}' from: {1}\nError: {2}", nodeId, def, ex.Message); return(null); } }