Data for an srb nozzle, including gimbal adjustment data and ISP curve adjustment.
Inheritance: SSTUTools.SingleModelData
        /// <summary>
        /// Updates the mount module from user input in the editor
        /// </summary>
        /// <param name="newMount"></param>
        /// <param name="updateSymmetry"></param>
        private void updateMountFromEditor(String newMount, bool updateSymmetry)
        {
            SRBNozzleData mod = Array.Find(nozzleModules, m => m.name == newMount);
            if (mod != null && mod != currentNozzleModule)
            {
                //finally, clear any existing models from prefab, and initialize the currently configured models
                resetTransformParents();

                currentNozzleModule.destroyCurrentModel();
                currentNozzleModule = mod;
                currentNozzleModule.setupModel(part, part.transform.FindRecursive(baseTransformName), ModelOrientation.BOTTOM);
                currentNozzleName = currentNozzleModule.name;
                currentGimbalOffset = 0;
            }
            if (!currentNozzleModule.isValidTextureSet(currentNozzleTexture))
            {
                currentNozzleTexture = currentNozzleModule.modelDefinition.defaultTextureSet;
            }
            currentNozzleModule.enableTextureSet(currentNozzleTexture);
            updateModelScaleAndPosition();
            updatePartResources();
            updatePartMass();
            updateAttachnodes(true);
            updateThrustOutput();
            currentNozzleModule.setupTransformDefaults(part.transform.FindRecursive(thrustTransformName), part.transform.FindRecursive(gimbalTransformName));
            updateGimbalOffset();
            updateEngineISP();
            updateEditorValues();
            updateGui();

            if (updateSymmetry)
            {
                foreach (Part p in part.symmetryCounterparts)
                {
                    p.GetComponent<SSTUModularBooster>().updateMountFromEditor(newMount, false);
                }
                GameEvents.onEditorShipModified.Fire(EditorLogic.fetch.ship);
            }
        }
        /// <summary>
        /// Updates the mount module from user input in the editor
        /// </summary>
        /// <param name="newMount"></param>
        /// <param name="updateSymmetry"></param>
        private void updateMountFromEditor(String newMount, bool updateSymmetry)
        {
            SRBNozzleData mod = Array.Find(nozzleModules, m => m.name == newMount);
            if (mod != null && mod != currentNozzleModule)
            {
                //finally, clear any existing models from prefab, and initialize the currently configured models
                resetTransformParents();

                currentNozzleModule.destroyCurrentModel();
                currentNozzleModule = mod;
                currentNozzleModule.setupModel(part.transform.FindRecursive(baseTransformName), ModelOrientation.BOTTOM);
                currentGimbalOffset = 0;
            }
            currentNozzleName = currentNozzleModule.name;
            if (!currentNozzleModule.isValidTextureSet(currentNozzleTexture))
            {
                currentNozzleTexture = currentNozzleModule.getDefaultTextureSet();
            }
            currentNozzleModule.enableTextureSet(currentNozzleTexture);
            currentNozzleModule.updateTextureUIControl(this, "currentNozzleTexture", currentNozzleTexture);
            updateModelScaleAndPosition();
            updateEffectsScale();
            updateContainerVolume();
            updatePartMass();
            updatePartCost();
            updateAttachnodes(true);
            currentNozzleModule.setupTransformDefaults(part.transform.FindRecursive(thrustTransformName), part.transform.FindRecursive(gimbalTransformName));
            updateGimbalOffset();
            updateEngineISP();
            updateEditorValues();
            updateThrustOutput();
            float val = currentNozzleModule.gimbalAdjustmentRange;
            this.updateUIFloatEditControl("currentGimbalOffset", -val, val, 2f, 1f, 0.1f, true, currentGimbalOffset);
            updateGui();

            if (updateSymmetry)
            {
                foreach (Part p in part.symmetryCounterparts)
                {
                    p.GetComponent<SSTUModularBooster>().updateMountFromEditor(newMount, false);
                }
            }
            SSTUStockInterop.fireEditorUpdate();
            SSTUModInterop.onPartGeometryUpdate(part, true);
        }
        /// <summary>
        /// Loads the current configuration from the cached persistent config node data
        /// </summary>
        private void loadConfigNodeData()
        {
            ConfigNode node = SSTUConfigNodeUtils.parseConfigNode(configNodeData);

            //load singular fuel type data from config node;
            //using a node so that it may have the custom fields defined for mass fraction/etc on a per-part basis
            fuelTypeData = new FuelTypeData(node.GetNode("FUELTYPE"));

            //load all main tank model datas
            mainModules = SRBModelData.parseSRBModels(node.GetNodes("MAINMODEL"));
            currentMainModule = Array.Find(mainModules, m => m.name == currentMainName);
            if (currentMainModule == null)
            {
                currentMainModule = mainModules[0];
                currentMainName = currentMainModule.name;
            }
            if (!currentMainModule.isValidTextureSet(currentMainTexture)) { currentMainTexture = currentMainModule.modelDefinition.defaultTextureSet; }

            //load nose modules from NOSE nodes
            ConfigNode[] noseNodes = node.GetNodes("NOSE");
            ConfigNode noseNode;
            int length = noseNodes.Length;
            List<MountModelData> noseModulesTemp = new List<MountModelData>();
            for (int i = 0; i < length; i++)
            {
                noseNode = noseNodes[i];
                noseModulesTemp.Add(new MountModelData(noseNode));
            }
            this.noseModules = noseModulesTemp.ToArray();
            currentNoseModule = Array.Find(this.noseModules, m => m.name == currentNoseName);
            if (currentNoseModule == null)
            {
                currentNoseModule = this.noseModules[0];//not having a mount defined is an error, at least one mount must be defined, crashing at this point is acceptable
                currentNoseName = currentNoseModule.name;
            }
            if (!currentNoseModule.isValidTextureSet(currentNoseTexture)) { currentNoseTexture = currentNoseModule.modelDefinition.defaultTextureSet; }

            //load nose modules from NOZZLE nodes
            ConfigNode[] nozzleNodes = node.GetNodes("NOZZLE");
            ConfigNode nozzleNode;
            length = nozzleNodes.Length;
            List<SRBNozzleData> nozzleModulesTemp = new List<SRBNozzleData>();
            for (int i = 0; i < length; i++)
            {
                nozzleNode = nozzleNodes[i];
                nozzleModulesTemp.Add(new SRBNozzleData(nozzleNode));
            }
            this.nozzleModules = nozzleModulesTemp.ToArray();
            currentNozzleModule = Array.Find(this.nozzleModules, m => m.name == currentNozzleName);
            if (currentNozzleModule == null)
            {
                currentNozzleModule = this.nozzleModules[0];//not having a mount defined is an error, at least one mount must be defined, crashing at this point is acceptable
                currentNozzleName = currentNozzleModule.name;
            }
            if (!currentNozzleModule.isValidTextureSet(currentNozzleTexture)) { currentNozzleTexture = currentNozzleModule.modelDefinition.defaultTextureSet; }

            //reset existing gimbal/thrust transforms, remove them from the model hierarchy
            resetTransformParents();//this resets the thrust transform parent in case it was changed during prefab; we don't want to delete the thrust transform
            Transform parentTransform = part.transform.FindRecursive("model").FindOrCreate(baseTransformName);
            //finally, clear any existing models from prefab, and initialize the currently configured models
            SSTUUtils.destroyChildren(parentTransform);
            currentNoseModule.setupModel(part, parentTransform, ModelOrientation.TOP);
            currentNozzleModule.setupModel(part, parentTransform, ModelOrientation.BOTTOM);
            currentMainModule.setupModel(part, parentTransform, ModelOrientation.CENTRAL);
            //lastly, re-insert gimbal and thrust transforms into model hierarchy and reset default gimbal rotation offset
            currentNozzleModule.setupTransformDefaults(part.transform.FindRecursive(thrustTransformName), part.transform.FindRecursive(gimbalTransformName));
        }
        /// <summary>
        /// Loads the current configuration from the cached persistent config node data
        /// </summary>
        private void loadConfigNodeData()
        {
            ConfigNode node = SSTUConfigNodeUtils.parseConfigNode(configNodeData);

            //load all main tank model datas
            mainModules = SRBModelData.parseSRBModels(node.GetNodes("MAINMODEL"));
            currentMainModule = Array.Find(mainModules, m => m.name == currentMainName);
            if (currentMainModule == null)
            {
                currentMainModule = mainModules[0];
                currentMainName = currentMainModule.name;
            }
            if (!currentMainModule.isValidTextureSet(currentMainTexture))
            {
                currentMainTexture = currentMainModule.getDefaultTextureSet();
            }

            //load nose modules from NOSE nodes
            ConfigNode[] noseNodes = node.GetNodes("NOSE");
            ConfigNode noseNode;
            int length = noseNodes.Length;
            List<SingleModelData> noseModulesTemp = new List<SingleModelData>();
            for (int i = 0; i < length; i++)
            {
                noseNode = noseNodes[i];
                noseModulesTemp.Add(new SingleModelData(noseNode));
            }
            this.noseModules = noseModulesTemp.ToArray();
            currentNoseModule = Array.Find(this.noseModules, m => m.name == currentNoseName);
            if (currentNoseModule == null)
            {
                currentNoseModule = this.noseModules[0];//not having a mount defined is an error, at least one mount must be defined, crashing at this point is acceptable
                currentNoseName = currentNoseModule.name;
            }
            if (!currentNoseModule.isValidTextureSet(currentNoseTexture))
            {
                currentNoseTexture = currentNoseModule.getDefaultTextureSet();
            }

            //load nose modules from NOZZLE nodes
            ConfigNode[] nozzleNodes = node.GetNodes("NOZZLE");
            ConfigNode nozzleNode;
            length = nozzleNodes.Length;
            List<SRBNozzleData> nozzleModulesTemp = new List<SRBNozzleData>();
            for (int i = 0; i < length; i++)
            {
                nozzleNode = nozzleNodes[i];
                nozzleModulesTemp.Add(new SRBNozzleData(nozzleNode));
            }
            this.nozzleModules = nozzleModulesTemp.ToArray();
            currentNozzleModule = Array.Find(this.nozzleModules, m => m.name == currentNozzleName);
            if (currentNozzleModule == null)
            {
                currentNozzleModule = this.nozzleModules[0];//not having a mount defined is an error, at least one mount must be defined, crashing at this point is acceptable
                currentNozzleName = currentNozzleModule.name;
            }
            if (!currentNozzleModule.isValidTextureSet(currentNozzleTexture))
            {
                currentNozzleTexture = currentNozzleModule.getDefaultTextureSet();
            }

            //reset existing gimbal/thrust transforms, remove them from the model hierarchy
            resetTransformParents();//this resets the thrust transform parent in case it was changed during prefab; we don't want to delete the thrust transform
            Transform parentTransform = part.transform.FindRecursive("model").FindOrCreate(baseTransformName);
            //finally, clear any existing models from prefab, and initialize the currently configured models
            SSTUUtils.destroyChildren(parentTransform);
            currentNoseModule.setupModel(parentTransform, ModelOrientation.TOP);
            currentNozzleModule.setupModel(parentTransform, ModelOrientation.BOTTOM);
            currentMainModule.setupModel(parentTransform, ModelOrientation.CENTRAL);
            //lastly, re-insert gimbal and thrust transforms into model hierarchy and reset default gimbal rotation offset
            currentNozzleModule.setupTransformDefaults(part.transform.FindRecursive(thrustTransformName), part.transform.FindRecursive(gimbalTransformName));

            //if had custom thrust curve data, reload it now (else it will default to whatever is on the engine)
            if (!string.IsNullOrEmpty(thrustCurveData))
            {
                thrustCurveCache = new FloatCurve();
                string[] keySplits = thrustCurveData.Split(':');
                string[] valSplits;
                int len = keySplits.Length;
                float key, value, inTan, outTan;
                for (int i = 0; i < len; i++)
                {
                    valSplits = keySplits[i].Split(',');
                    key = float.Parse(valSplits[0]);
                    value = float.Parse(valSplits[1]);
                    inTan = float.Parse(valSplits[2]);
                    outTan = float.Parse(valSplits[3]);
                    thrustCurveCache.Add(key, value, inTan, outTan);
                }
            }
            if (!string.IsNullOrEmpty(presetCurveName))
            {
                ConfigNode[] presetNodes = GameDatabase.Instance.GetConfigNodes("SSTU_THRUSTCURVE");
                int len = presetNodes.Length;
                for (int i = 0; i < len; i++)
                {
                    if (presetNodes[i].GetStringValue("name") == presetCurveName)
                    {
                        thrustCurveCache = presetNodes[i].GetFloatCurve("curve");
                        break;
                    }
                }
            }
        }