private void ApplyChanges()
        {
            MASConfig.VerboseLogging                = verboseLogging;
            MASConfig.LuaUpdatePriority             = luaUpdatePriority;
            MASConfig.CameraTextureScale            = cameraTextureScale;
            MASConfig.navigation.generalPropagation = generalPropagation;
            MASConfig.navigation.NDBPropagation     = NDBPropagation;
            MASConfig.navigation.VORPropagation     = VORPropagation;
            MASConfig.navigation.DMEPropagation     = DMEPropagation;

            MASLoader.UpdateHorizonDistance();
        }
Example #2
0
        /// <summary>
        /// Read our config settings, setting defaults where needed.
        /// </summary>
        /// <param name="node"></param>
        public override void OnLoad(ConfigNode node)
        {
            if (!node.TryGetValue("VerboseLogging", ref VerboseLogging))
            {
                VerboseLogging = true;
            }

            if (!node.TryGetValue("HideGui", ref HideGui))
            {
                HideGui = false;
            }

            if (!node.TryGetValue("ElectricCharge", ref ElectricCharge))
            {
                ElectricCharge = "ElectricCharge";
            }

            if (!node.TryGetValue("LuaUpdatePriority", ref LuaUpdatePriority))
            {
                LuaUpdatePriority = 1;
            }

            if (!node.TryGetValue("CameraTextureScale", ref CameraTextureScale))
            {
                CameraTextureScale = 0;
            }

            if (!node.TryGetValue("GeneralPropagation", ref navigation.generalPropagation))
            {
                navigation.generalPropagation = 2.0f;
            }

            if (!node.TryGetValue("NDBPropagation", ref navigation.NDBPropagation))
            {
                navigation.NDBPropagation = 1.0f;
            }

            if (!node.TryGetValue("VORPropagation", ref navigation.VORPropagation))
            {
                navigation.VORPropagation = 1.2f;
            }

            if (!node.TryGetValue("DMEPropagation", ref navigation.DMEPropagation))
            {
                navigation.DMEPropagation = 1.4f;
            }

            MASLoader.UpdateHorizonDistance();
        }
Example #3
0
        internal MASComponentTextLabel(ConfigNode config, InternalProp prop, MASFlightComputer comp)
            : base(config, prop, comp)
        {
            string transform = string.Empty;

            if (!config.TryGetValue("transform", ref transform))
            {
                throw new ArgumentException("Missing 'transform' in TEXT_LABEL " + name);
            }

            string fontName = string.Empty;

            if (!config.TryGetValue("font", ref fontName))
            {
                throw new ArgumentException("Invalid or missing 'font' in TEXT_LABEL " + name);
            }

            string    styleStr = string.Empty;
            FontStyle style    = FontStyle.Normal;

            if (config.TryGetValue("style", ref styleStr))
            {
                style = MdVTextMesh.FontStyle(styleStr);
            }

            string text = string.Empty;

            if (!config.TryGetValue("text", ref text))
            {
                throw new ArgumentException("Invalid or missing 'text' in TEXT_LABEL " + name);
            }

            if (!config.TryGetValue("fontSize", ref fontSize))
            {
                throw new ArgumentException("Invalid or missing 'fontSize' in TEXT_LABEL " + name);
            }

            Vector2 transformOffset = Vector2.zero;

            if (!config.TryGetValue("transformOffset", ref transformOffset))
            {
                transformOffset = Vector2.zero;
            }

            Transform textObjTransform = prop.FindModelTransform(transform);
            Vector3   localScale       = prop.transform.localScale;

            Transform offsetTransform = new GameObject().transform;

            offsetTransform.gameObject.name  = Utility.ComposeObjectName(this.GetType().Name, name, prop.propID);
            offsetTransform.gameObject.layer = textObjTransform.gameObject.layer;
            offsetTransform.SetParent(textObjTransform, false);
            offsetTransform.Translate(transformOffset.x * localScale.x, transformOffset.y * localScale.y, 0.0f);

            textObj = offsetTransform.gameObject.AddComponent <MdVTextMesh>();

            Font font = MASLoader.GetFont(fontName.Trim());

            if (font == null)
            {
                throw new ArgumentNullException("Unable to load font " + fontName + " in TEXT_LABEL " + name);
            }

            float lineSpacing = 1.0f;

            if (!config.TryGetValue("lineSpacing", ref lineSpacing))
            {
                lineSpacing = 1.0f;
            }

            float sizeScalar = 32.0f / (float)font.fontSize;

            textObj.SetFont(font, font.fontSize, fontSize * 0.00005f * sizeScalar);
            textObj.SetLineSpacing(lineSpacing);
            textObj.fontStyle = style;

            string passiveColorStr = string.Empty;

            if (!config.TryGetValue("passiveColor", ref passiveColorStr))
            {
                throw new ArgumentException("Invalid or missing 'passiveColor' in TEXT_LABEL " + name);
            }
            else
            {
                Color32 namedColor;
                if (comp.TryGetNamedColor(passiveColorStr, out namedColor))
                {
                    passiveColor = namedColor;
                }
                else
                {
                    string[] startColors = Utility.SplitVariableList(passiveColorStr);
                    if (startColors.Length < 3 || startColors.Length > 4)
                    {
                        throw new ArgumentException("passiveColor does not contain 3 or 4 values in TEXT_LABEL " + name);
                    }

                    variableRegistrar.RegisterVariableChangeCallback(startColors[0], (double newValue) =>
                    {
                        passiveColor.r = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        if (blend)
                        {
                            UpdateBlendColor();
                        }
                        else
                        {
                            UpdateBooleanColor();
                        }
                    });

                    variableRegistrar.RegisterVariableChangeCallback(startColors[1], (double newValue) =>
                    {
                        passiveColor.g = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        if (blend)
                        {
                            UpdateBlendColor();
                        }
                        else
                        {
                            UpdateBooleanColor();
                        }
                    });

                    variableRegistrar.RegisterVariableChangeCallback(startColors[2], (double newValue) =>
                    {
                        passiveColor.b = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        if (blend)
                        {
                            UpdateBlendColor();
                        }
                        else
                        {
                            UpdateBooleanColor();
                        }
                    });

                    if (startColors.Length == 4)
                    {
                        variableRegistrar.RegisterVariableChangeCallback(startColors[3], (double newValue) =>
                        {
                            passiveColor.a = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                            if (blend)
                            {
                                UpdateBlendColor();
                            }
                            else
                            {
                                UpdateBooleanColor();
                            }
                        });
                    }
                }
            }
            // Final validations
            bool   usesMulticolor = false;
            string activeColorStr = string.Empty;

            if (config.TryGetValue("activeColor", ref activeColorStr))
            {
                usesMulticolor = true;

                Color32 namedColor;
                if (comp.TryGetNamedColor(activeColorStr, out namedColor))
                {
                    activeColor = namedColor;
                }
                else
                {
                    string[] startColors = Utility.SplitVariableList(activeColorStr);
                    if (startColors.Length < 3 || startColors.Length > 4)
                    {
                        throw new ArgumentException("activeColor does not contain 3 or 4 values in TEXT_LABEL " + name);
                    }

                    variableRegistrar.RegisterVariableChangeCallback(startColors[0], (double newValue) =>
                    {
                        activeColor.r = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        if (blend)
                        {
                            UpdateBlendColor();
                        }
                        else
                        {
                            UpdateBooleanColor();
                        }
                    });

                    variableRegistrar.RegisterVariableChangeCallback(startColors[1], (double newValue) =>
                    {
                        activeColor.g = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        if (blend)
                        {
                            UpdateBlendColor();
                        }
                        else
                        {
                            UpdateBooleanColor();
                        }
                    });

                    variableRegistrar.RegisterVariableChangeCallback(startColors[2], (double newValue) =>
                    {
                        activeColor.b = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        if (blend)
                        {
                            UpdateBlendColor();
                        }
                        else
                        {
                            UpdateBooleanColor();
                        }
                    });

                    if (startColors.Length == 4)
                    {
                        variableRegistrar.RegisterVariableChangeCallback(startColors[3], (double newValue) =>
                        {
                            activeColor.a = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                            if (blend)
                            {
                                UpdateBlendColor();
                            }
                            else
                            {
                                UpdateBooleanColor();
                            }
                        });
                    }
                }
            }

            string anchor = string.Empty;

            if (!config.TryGetValue("anchor", ref anchor))
            {
                anchor = string.Empty;
            }

            if (!string.IsNullOrEmpty(anchor))
            {
                if (anchor == TextAnchor.LowerCenter.ToString())
                {
                    textObj.anchor = TextAnchor.LowerCenter;
                }
                else if (anchor == TextAnchor.LowerLeft.ToString())
                {
                    textObj.anchor = TextAnchor.LowerLeft;
                }
                else if (anchor == TextAnchor.LowerRight.ToString())
                {
                    textObj.anchor = TextAnchor.LowerRight;
                }
                else if (anchor == TextAnchor.MiddleCenter.ToString())
                {
                    textObj.anchor = TextAnchor.MiddleCenter;
                }
                else if (anchor == TextAnchor.MiddleLeft.ToString())
                {
                    textObj.anchor = TextAnchor.MiddleLeft;
                }
                else if (anchor == TextAnchor.MiddleRight.ToString())
                {
                    textObj.anchor = TextAnchor.MiddleRight;
                }
                else if (anchor == TextAnchor.UpperCenter.ToString())
                {
                    textObj.anchor = TextAnchor.UpperCenter;
                }
                else if (anchor == TextAnchor.UpperLeft.ToString())
                {
                    textObj.anchor = TextAnchor.UpperLeft;
                }
                else if (anchor == TextAnchor.UpperRight.ToString())
                {
                    textObj.anchor = TextAnchor.UpperRight;
                }
                else
                {
                    Utility.LogError(this, "Unrecognized anchor '{0}' in config for {1} ({2})", anchor, prop.propID, prop.propName);
                }
            }

            string alignment = string.Empty;

            if (!config.TryGetValue("alignment", ref alignment))
            {
                alignment = string.Empty;
            }
            if (!string.IsNullOrEmpty(alignment))
            {
                if (alignment == TextAlignment.Center.ToString())
                {
                    textObj.alignment = TextAlignment.Center;
                }
                else if (alignment == TextAlignment.Left.ToString())
                {
                    textObj.alignment = TextAlignment.Left;
                }
                else if (alignment == TextAlignment.Right.ToString())
                {
                    textObj.alignment = TextAlignment.Right;
                }
                else
                {
                    Utility.LogError(this, "Unrecognized alignment '{0}' in config for {1} ({2})", alignment, prop.propID, prop.propName);
                }
            }

            string emissive = string.Empty;

            config.TryGetValue("emissive", ref emissive);

            if (string.IsNullOrEmpty(emissive))
            {
                if (usesMulticolor)
                {
                    emissiveMode = EmissiveMode.active;
                }
                else
                {
                    emissiveMode = EmissiveMode.always;
                }
            }
            else if (emissive.ToLower() == EmissiveMode.always.ToString())
            {
                emissiveMode = EmissiveMode.always;
            }
            else if (emissive.ToLower() == EmissiveMode.never.ToString())
            {
                emissiveMode = EmissiveMode.never;
            }
            else if (emissive.ToLower() == EmissiveMode.active.ToString())
            {
                emissiveMode = EmissiveMode.active;
            }
            else if (emissive.ToLower() == EmissiveMode.passive.ToString())
            {
                emissiveMode = EmissiveMode.passive;
            }
            else if (emissive.ToLower() == EmissiveMode.flash.ToString())
            {
                emissiveMode = EmissiveMode.flash;
            }
            else
            {
                Utility.LogError(this, "Unrecognized emissive mode '{0}' in config for {1} ({2})", emissive, prop.propID, prop.propName);
                emissiveMode = EmissiveMode.always;
            }

            string variableName = string.Empty;

            if (!config.TryGetValue("variable", ref variableName) || string.IsNullOrEmpty(variableName))
            {
                if (usesMulticolor)
                {
                    throw new ArgumentException("Invalid or missing 'variable' in TEXT_LABEL " + name);
                }
            }
            else if (!usesMulticolor)
            {
                throw new ArgumentException("Invalid or missing 'activeColor' in TEXT_LABEL " + name);
            }

            config.TryGetValue("blend", ref blend);

            if (emissiveMode == EmissiveMode.flash)
            {
                if (blend == false && config.TryGetValue("flashRate", ref flashRate) && flashRate > 0.0f)
                {
                    this.comp = comp;
                    comp.RegisterFlashCallback(flashRate, FlashToggle);
                }
                else
                {
                    emissiveMode = EmissiveMode.active;
                }
            }

            bool immutable = false;

            if (!config.TryGetValue("oneshot", ref immutable))
            {
                immutable = false;
            }

            textObj.SetColor(passiveColor);
            textObj.SetText(text, immutable, false, comp, prop);

            UpdateShader();

            if (!string.IsNullOrEmpty(variableName))
            {
                variableRegistrar.RegisterVariableChangeCallback(variableName, VariableCallback);
            }
        }
Example #4
0
        /// <summary>
        /// Startup, initialize, configure, etc.
        /// </summary>
        public void Start()
        {
            if (HighLogic.LoadedSceneIsFlight)
            {
                try
                {
                    MASFlightComputer comp = MASFlightComputer.Instance(internalProp.part);
                    if (comp == null)
                    {
                        throw new ArgumentNullException("Failed to find MASFlightComputer initializing MASMonitor");
                    }
                    variableRegistrar = new VariableRegistrar(comp, internalProp);

                    if (string.IsNullOrEmpty(screenTransform))
                    {
                        throw new ArgumentException("Missing 'transform' in MASMonitor");
                    }

                    if (string.IsNullOrEmpty(layer))
                    {
                        throw new ArgumentException("Missing 'layer' in MASMonitor");
                    }

                    if (string.IsNullOrEmpty(font))
                    {
                        throw new ArgumentException("Missing 'font' in MASMonitor");
                    }

                    if (string.IsNullOrEmpty(textColor))
                    {
                        throw new ArgumentException("Missing 'textColor' in MASMonitor");
                    }
                    else
                    {
                        textColor_ = Utility.ParseColor32(textColor, comp);
                    }

                    if (string.IsNullOrEmpty(backgroundColor))
                    {
                        throw new ArgumentException("Missing 'backgroundColor' in MASMonitor");
                    }
                    else
                    {
                        backgroundColor_ = Utility.ParseColor32(backgroundColor, comp);
                    }

                    if (screenSize.x <= 0.0f || screenSize.y <= 0.0f)
                    {
                        throw new ArgumentException("Invalid 'screenSize' in MASMonitor");
                    }

                    if (fontSize.x <= 0.0f || fontSize.y <= 0.0f)
                    {
                        throw new ArgumentException("Invalid 'fontSize' in MASMonitor");
                    }

                    screenWidth  = (int)screenSize.x;
                    screenHeight = (int)screenSize.y;

                    screenSpace       = new GameObject();
                    screenSpace.name  = Utility.ComposeObjectName(internalProp.propName, this.GetType().Name, screenSpace.GetInstanceID());
                    screenSpace.layer = drawingLayer;
                    screenSpace.transform.position = Vector3.zero;
                    screenSpace.SetActive(true);

                    screen = new RenderTexture(screenWidth, screenHeight, 24, RenderTextureFormat.ARGB32);
                    if (screen == null)
                    {
                        throw new ArgumentNullException("Failed to find create " + screenWidth + " x " + screenHeight + " render texture initializing MASMonitor");
                    }
                    if (!screen.IsCreated())
                    {
                        screen.Create();
                        screen.DiscardContents();
                    }

                    Camera.onPreCull    += EnablePage;
                    Camera.onPostRender += DisablePage;

                    screenCamera                      = screenSpace.AddComponent <Camera>();
                    screenCamera.enabled              = true; // Enable = "auto-draw"
                    screenCamera.orthographic         = true;
                    screenCamera.aspect               = screenSize.x / screenSize.y;
                    screenCamera.eventMask            = 0;
                    screenCamera.farClipPlane         = 1.0f + depthDelta;
                    screenCamera.nearClipPlane        = depthDelta;
                    screenCamera.orthographicSize     = screenSize.y * 0.5f;
                    screenCamera.cullingMask          = 1 << drawingLayer;
                    screenCamera.transparencySortMode = TransparencySortMode.Orthographic;
                    screenCamera.transform.position   = Vector3.zero;
                    screenCamera.transform.LookAt(new Vector3(0.0f, 0.0f, maxDepth), Vector3.up);
                    screenCamera.backgroundColor = backgroundColor_;
                    screenCamera.clearFlags      = CameraClearFlags.SolidColor;
                    screenCamera.targetTexture   = screen;

                    Transform screenTransformLoc = internalProp.FindModelTransform(screenTransform);
                    if (screenTransformLoc == null)
                    {
                        throw new ArgumentNullException("Failed to find screenTransform \"" + screenTransform + "\" initializing MASMonitor");
                    }
                    Material screenMat = screenTransformLoc.GetComponent <Renderer>().material;
                    string[] layers    = layer.Split();
                    for (int i = layers.Length - 1; i >= 0; --i)
                    {
                        screenMat.SetTexture(layers[i].Trim(), screen);
                    }

                    defaultFont = MASLoader.GetFont(font.Trim());

                    if (!string.IsNullOrEmpty(style))
                    {
                        defaultStyle = MdVTextMesh.FontStyle(style.Trim());
                    }

                    ConfigNode moduleConfig = Utility.GetPropModuleConfigNode(internalProp.propName, ClassName);
                    if (moduleConfig == null)
                    {
                        throw new ArgumentNullException("No ConfigNode found for MASMonitor in " + internalProp.propName + "!");
                    }

                    // If an initialization script was supplied, call it.
                    if (!string.IsNullOrEmpty(startupScript))
                    {
                        Action startup = comp.GetAction(startupScript, internalProp);
                        startup();
                    }

                    string[] pages    = moduleConfig.GetValues("page");
                    int      numPages = pages.Length;
                    for (int i = 0; i < numPages; ++i)
                    {
                        pages[i] = pages[i].Trim();
                        ConfigNode pageConfig = Utility.GetPageConfigNode(pages[i]);
                        if (pageConfig == null)
                        {
                            throw new ArgumentException("No ConfigNode found for page " + pages[i] + " in MASMonitor in " + internalProp.propName + "!");
                        }

                        // Parse the page node
                        MASPage newPage = new MASPage(pageConfig, internalProp, comp, this, screenSpace.transform);
                        if (i == 0)
                        {
                            // Select the default page as the current page
                            currentPage = newPage;
                        }

                        newPage.SetPageActive(false);

                        page.Add(pages[i], newPage);
                        //Utility.LogMessage(this, "Page = {0}", pages[i]);
                    }
                    //HackWalkTransforms(screenSpace.transform, 0);
                    if (!string.IsNullOrEmpty(monitorID))
                    {
                        string variableName = "fc.GetPersistent(\"" + monitorID.Trim() + "\")";
                        pageSelector = variableRegistrar.RegisterVariableChangeCallback(variableName, PageChanged, false);
                        // See if we have a saved page to restore.
                        if (!string.IsNullOrEmpty(pageSelector.AsString()) && page.ContainsKey(pageSelector.AsString()))
                        {
                            currentPage = page[pageSelector.AsString()];
                        }
                        comp.RegisterMonitor(monitorID, internalProp, this);
                    }
                    currentPage.SetPageActive(true);
                    initialized = true;
                    Utility.LogMessage(this, "Configuration complete in prop #{0} ({1}) with {2} pages", internalProp.propID, internalProp.propName, numPages);
                }
                catch (Exception e)
                {
                    Utility.ComplainLoudly("MASMonitor configuration failed.");
                    Utility.LogError(this, "Failed to configure prop #{0} ({1})", internalProp.propID, internalProp.propName);
                    Utility.LogError(this, e.ToString());
                }
            }
        }
Example #5
0
        internal MASPageText(ConfigNode config, InternalProp prop, MASFlightComputer comp, MASMonitor monitor, Transform pageRoot, float depth)
            : base(config, prop, comp)
        {
            if (!config.TryGetValue("text", ref text))
            {
                string textfile = string.Empty;
                if (!config.TryGetValue("textfile", ref textfile))
                {
                    string rpmModText = string.Empty;
                    if (!config.TryGetValue("textmethod", ref rpmModText))
                    {
                        throw new ArgumentException("Unable to find 'text', 'textfile', or 'textmethod' in TEXT " + name);
                    }

                    string[] rpmMod = rpmModText.Split(':');
                    if (rpmMod.Length != 2)
                    {
                        throw new ArgumentException("Invalid 'textmethod' in TEXT " + name);
                    }
                    bool moduleFound = false;

                    int numModules = prop.internalModules.Count;
                    int moduleIndex;
                    for (moduleIndex = 0; moduleIndex < numModules; ++moduleIndex)
                    {
                        if (prop.internalModules[moduleIndex].ClassName == rpmMod[0])
                        {
                            moduleFound = true;
                            break;
                        }
                    }

                    if (moduleFound)
                    {
                        rpmModule = prop.internalModules[moduleIndex];
                        Type       moduleType = prop.internalModules[moduleIndex].GetType();
                        MethodInfo method     = moduleType.GetMethod(rpmMod[1]);
                        if (method != null && method.GetParameters().Length == 2 && method.GetParameters()[0].ParameterType == typeof(int) && method.GetParameters()[1].ParameterType == typeof(int))
                        {
                            rpmModuleTextMethod = DynamicMethodFactory.CreateFunc <object, int, int, string>(method);
                        }
                    }

                    if (rpmModuleTextMethod != null)
                    {
                        this.comp = comp;
                        this.prop = prop;
                    }
                    text = " ";
                }
                else
                {
                    // Load text
                    text = string.Join(Environment.NewLine, File.ReadAllLines(KSPUtil.ApplicationRootPath + "GameData/" + textfile.Trim(), Encoding.UTF8));
                }
            }

            string localFonts = string.Empty;

            if (!config.TryGetValue("font", ref localFonts))
            {
                localFonts = string.Empty;
            }

            string    styleStr = string.Empty;
            FontStyle style    = FontStyle.Normal;

            if (config.TryGetValue("style", ref styleStr))
            {
                style = MdVTextMesh.FontStyle(styleStr);
            }
            else
            {
                style = monitor.defaultStyle;
            }

            Vector2 fontSize = Vector2.zero;

            if (!config.TryGetValue("fontSize", ref fontSize) || fontSize.x < 0.0f || fontSize.y < 0.0f)
            {
                fontSize = monitor.fontSize;
            }

            Color32 textColor;
            string  textColorStr = string.Empty;

            if (!config.TryGetValue("textColor", ref textColorStr) || string.IsNullOrEmpty(textColorStr))
            {
                textColor = monitor.textColor_;
            }
            else
            {
                textColor = Utility.ParseColor32(textColorStr, comp);
            }

            // Position is based on default font size
            fontScale = monitor.fontSize;
            // Position is based on local font size.
            //fontScale = fontSize;

            string variableName = string.Empty;

            if (config.TryGetValue("variable", ref variableName))
            {
                variableName = variableName.Trim();
            }

            // Set up our text.
            imageOrigin = pageRoot.position + new Vector3(monitor.screenSize.x * -0.5f, monitor.screenSize.y * 0.5f, depth);

            meshObject                    = new GameObject();
            meshObject.name               = Utility.ComposeObjectName(pageRoot.gameObject.name, this.GetType().Name, name, (int)(-depth / MASMonitor.depthDelta));
            meshObject.layer              = pageRoot.gameObject.layer;
            meshObject.transform.parent   = pageRoot;
            meshObject.transform.position = imageOrigin;

            string positionString = string.Empty;

            if (config.TryGetValue("position", ref positionString))
            {
                string[] positions = Utility.SplitVariableList(positionString);
                if (positions.Length != 2)
                {
                    throw new ArgumentException("position does not contain 2 values in TEXT " + name);
                }

                variableRegistrar.RegisterVariableChangeCallback(positions[0], (double newValue) =>
                {
                    position.x = (float)newValue * fontScale.x;
                    meshObject.transform.position = imageOrigin + new Vector3(position.x, -position.y, 0.0f);
                });

                variableRegistrar.RegisterVariableChangeCallback(positions[1], (double newValue) =>
                {
                    position.y = (float)newValue * fontScale.y;
                    meshObject.transform.position = imageOrigin + new Vector3(position.x, -position.y, 0.0f);
                });
            }

            textObj = meshObject.gameObject.AddComponent <MdVTextMesh>();

            Font font;

            if (string.IsNullOrEmpty(localFonts))
            {
                font = monitor.defaultFont;
            }
            else
            {
                font = MASLoader.GetFont(localFonts.Trim());
            }

            // We want to use a different shader for monitor displays.
            textObj.material = new Material(MASLoader.shaders["MOARdV/TextMonitor"]);
            textObj.SetFont(font, fontSize);
            textObj.SetColor(textColor);
            textObj.material.SetFloat(Shader.PropertyToID("_EmissiveFactor"), 1.0f);
            textObj.fontStyle = style;

            // text, immutable, preserveWhitespace, comp, prop
            textObj.SetText(text, false, true, comp, prop);
            RenderPage(false);

            if (!string.IsNullOrEmpty(variableName))
            {
                // Disable the mesh if we're in variable mode
                meshObject.SetActive(false);
                variableRegistrar.RegisterVariableChangeCallback(variableName, VariableCallback);
            }
            else
            {
                currentState = true;
            }

            if (rpmModuleTextMethod != null)
            {
                comp.StartCoroutine(TextMethodUpdate());
            }
        }
Example #6
0
        internal MASPageCompoundText(ConfigNode config, InternalProp prop, MASFlightComputer comp, MASMonitor monitor, Transform pageRoot, float depth)
            : base(config, prop, comp)
        {
            this.comp = comp;

            if (!config.TryGetValue("maxLines", ref maxLines))
            {
                throw new ArgumentException("Missing 'maxLines' in COMPOUND_TEXT " + name);
            }
            if (maxLines < 1)
            {
                throw new ArgumentException("'maxLines' must be greater than zero in COMPOUND_TEXT " + name);
            }

            string localFonts = string.Empty;

            if (!config.TryGetValue("font", ref localFonts))
            {
                localFonts = string.Empty;
            }

            string    styleStr = string.Empty;
            FontStyle style    = FontStyle.Normal;

            if (config.TryGetValue("style", ref styleStr))
            {
                style = MdVTextMesh.FontStyle(styleStr);
            }
            else
            {
                style = monitor.defaultStyle;
            }

            Vector2 fontSize = Vector2.zero;

            if (!config.TryGetValue("fontSize", ref fontSize) || fontSize.x < 0.0f || fontSize.y < 0.0f)
            {
                fontSize = monitor.fontSize;
            }

            lineAdvance = fontSize.y;

            Color32 textColor;
            string  textColorStr = string.Empty;

            if (!config.TryGetValue("textColor", ref textColorStr) || string.IsNullOrEmpty(textColorStr))
            {
                textColor = monitor.textColor_;
            }
            else
            {
                textColor = Utility.ParseColor32(textColorStr, comp);
            }

            // Set up our text.
            textOrigin = pageRoot.position + new Vector3(monitor.screenSize.x * -0.5f, monitor.screenSize.y * 0.5f, depth);

            rootObject                    = new GameObject();
            rootObject.name               = Utility.ComposeObjectName(pageRoot.gameObject.name, this.GetType().Name, name, (int)(-depth / MASMonitor.depthDelta));
            rootObject.layer              = pageRoot.gameObject.layer;
            rootObject.transform.parent   = pageRoot;
            rootObject.transform.position = textOrigin;

            string positionString = string.Empty;

            if (config.TryGetValue("position", ref positionString))
            {
                string[] positions = Utility.SplitVariableList(positionString);
                if (positions.Length != 2)
                {
                    throw new ArgumentException("position does not contain 2 values in COMPOUND_TEXT " + name);
                }

                variableRegistrar.RegisterVariableChangeCallback(positions[0], (double newValue) =>
                {
                    position.x = (float)newValue * monitor.fontSize.x;
                    rootObject.transform.position = textOrigin + new Vector3(position.x, -position.y, 0.0f);
                });

                variableRegistrar.RegisterVariableChangeCallback(positions[1], (double newValue) =>
                {
                    position.y = (float)newValue * monitor.fontSize.y;
                    rootObject.transform.position = textOrigin + new Vector3(position.x, -position.y, 0.0f);
                });
            }

            Font font;

            if (string.IsNullOrEmpty(localFonts))
            {
                font = monitor.defaultFont;
            }
            else
            {
                font = MASLoader.GetFont(localFonts.Trim());
            }

            List <CompoundPageText> textNodes = new List <CompoundPageText>();

            ConfigNode[] textConfigNodes = config.GetNodes("TEXT");
            foreach (ConfigNode textNode in textConfigNodes)
            {
                CompoundPageText cpt = new CompoundPageText();

                if (!textNode.TryGetValue("name", ref cpt.name))
                {
                    cpt.name = "anonymous";
                }

                string variableName = string.Empty;
                if (!textNode.TryGetValue("variable", ref variableName))
                {
                    variableName.Trim();
                }

                string text = string.Empty;
                if (textNode.TryGetValue("text", ref text))
                {
                    cpt.textObject                    = new GameObject();
                    cpt.textObject.name               = Utility.ComposeObjectName(pageRoot.gameObject.name, this.GetType().Name + textNodes.Count, cpt.name, (int)(-depth / MASMonitor.depthDelta));
                    cpt.textObject.layer              = rootObject.layer;
                    cpt.textObject.transform.parent   = rootObject.transform;
                    cpt.textObject.transform.position = rootObject.transform.position;

                    cpt.textMesh          = cpt.textObject.AddComponent <MdVTextMesh>();
                    cpt.textMesh.material = new Material(MASLoader.shaders["MOARdV/TextMonitor"]);
                    cpt.textMesh.SetFont(font, fontSize);
                    cpt.textMesh.SetColor(textColor);
                    cpt.textMesh.material.SetFloat(Shader.PropertyToID("_EmissiveFactor"), 1.0f);
                    cpt.textMesh.fontStyle = style;

                    // text, immutable, preserveWhitespace, comp, prop
                    cpt.textMesh.SetText(text, false, true, comp, prop);
                }

                // Process callbacks
                if (!string.IsNullOrEmpty(variableName))
                {
                    if (cpt.textObject != null)
                    {
                        cpt.textObject.SetActive(false);
                    }
                    variableRegistrar.RegisterVariableChangeCallback(variableName, (double newValue) => VariableCallback(newValue, cpt));
                }
                else
                {
                    cpt.currentState = true;
                    if (!coroutineActive)
                    {
                        coroutineActive = true;
                        comp.StartCoroutine(TextMethodUpdate());
                    }
                }

                textNodes.Add(cpt);
            }
            textElements = textNodes.ToArray();

            string masterVariableName = string.Empty;

            if (config.TryGetValue("variable", ref masterVariableName))
            {
                rootObject.SetActive(false);

                variableRegistrar.RegisterVariableChangeCallback(masterVariableName, VariableCallback);
            }
            else
            {
                rootObject.SetActive(true);
            }

            RenderPage(false);
        }
Example #7
0
        internal MASPageImage(ConfigNode config, InternalProp prop, MASFlightComputer comp, MASMonitor monitor, Transform pageRoot, float depth)
            : base(config, prop, comp)
        {
            string textureName = string.Empty;

            if (!config.TryGetValue("texture", ref textureName))
            {
                throw new ArgumentException("Unable to find 'texture' in IMAGE " + name);
            }
            Texture2D mainTexture = null;

            if (textureName == "%MAP_ICON%")
            {
                mainTexture = MASLoader.OrbitIconsAtlas();
            }
            else if (textureName == "%NAVBALL_ICON%")
            {
                mainTexture = GameDatabase.Instance.GetTexture("Squad/Props/IVANavBall/ManeuverNode_vectors", false);
            }
            else
            {
                if (textureName == "%FLAG%")
                {
                    textureName = prop.part.flagURL;
                }
                mainTexture = GameDatabase.Instance.GetTexture(textureName, false);
            }
            if (mainTexture == null)
            {
                throw new ArgumentException("Unable to find 'texture' " + textureName + " for IMAGE " + name);
            }

            bool wrap = true;

            if (config.TryGetValue("wrap", ref wrap))
            {
                mainTexture.wrapMode = (wrap) ? TextureWrapMode.Repeat : TextureWrapMode.Clamp;
            }

            string variableName = string.Empty;

            if (config.TryGetValue("variable", ref variableName))
            {
                variableName = variableName.Trim();
            }

            string rotationVariableName = string.Empty;

            if (config.TryGetValue("rotation", ref rotationVariableName))
            {
                config.TryGetValue("rotationOffset", ref rotationOffset);
            }

            // Need Mesh for UpdateVertices.
            mesh = new Mesh();
            string sizeString = string.Empty;

            if (!config.TryGetValue("size", ref sizeString))
            {
                size = new Vector2(mainTexture.width, mainTexture.height);
                UpdateVertices();
            }
            else
            {
                string[] sizes = Utility.SplitVariableList(sizeString);
                if (sizes.Length != 2)
                {
                    throw new ArgumentException("Invalid number of values for 'size' in IMAGE " + name);
                }

                variableRegistrar.RegisterVariableChangeCallback(sizes[0], (double newValue) =>
                {
                    size.x = (float)newValue;
                    UpdateVertices();
                });

                variableRegistrar.RegisterVariableChangeCallback(sizes[1], (double newValue) =>
                {
                    size.y = (float)newValue;
                    UpdateVertices();
                });
            }

            imageOrigin = pageRoot.position + new Vector3(monitor.screenSize.x * -0.5f, monitor.screenSize.y * 0.5f, depth);

            // Need imageObject for UpdatePosition.
            imageObject = new GameObject();
            imageObject.transform.parent = pageRoot;
            string positionString = string.Empty;

            if (!config.TryGetValue("position", ref positionString))
            {
                position = Vector2.zero;
                imageObject.transform.position = imageOrigin + new Vector3(position.x + rotationOffset.x + size.x * 0.5f, -(position.y + rotationOffset.y + size.y * 0.5f), 0.0f);
            }
            else
            {
                string[] pos = Utility.SplitVariableList(positionString);
                if (pos.Length != 2)
                {
                    throw new ArgumentException("Invalid number of values for 'position' in IMAGE " + name);
                }

                variableRegistrar.RegisterVariableChangeCallback(pos[0], (double newValue) =>
                {
                    position.x = (float)newValue;
                    imageObject.transform.position = imageOrigin + new Vector3(position.x + rotationOffset.x + size.x * 0.5f, -(position.y + rotationOffset.y + size.y * 0.5f), 0.0f);
                });

                variableRegistrar.RegisterVariableChangeCallback(pos[1], (double newValue) =>
                {
                    position.y = (float)newValue;
                    imageObject.transform.position = imageOrigin + new Vector3(position.x + rotationOffset.x + size.x * 0.5f, -(position.y + rotationOffset.y + size.y * 0.5f), 0.0f);
                });
            }

            // Set up our surface.
            imageObject.name  = Utility.ComposeObjectName(pageRoot.gameObject.name, this.GetType().Name, name, (int)(-depth / MASMonitor.depthDelta));
            imageObject.layer = pageRoot.gameObject.layer;

            // add renderer stuff
            MeshFilter meshFilter = imageObject.AddComponent <MeshFilter>();

            meshRenderer = imageObject.AddComponent <MeshRenderer>();

            mesh.uv = new[]
            {
                new Vector2(0.0f, 1.0f),
                Vector2.one,
                Vector2.zero,
                new Vector2(1.0f, 0.0f),
            };
            mesh.triangles = new[]
            {
                0, 1, 2,
                1, 3, 2
            };
            mesh.RecalculateBounds();
            mesh.UploadMeshData(false);
            meshFilter.mesh = mesh;

            imageMaterial             = new Material(MASLoader.shaders["MOARdV/Monitor"]);
            imageMaterial.mainTexture = mainTexture;
            meshRenderer.material     = imageMaterial;
            RenderPage(false);

            currentBlend = 0.0f;

            passiveColor = Color.white;
            activeColor  = Color.white;

            string passiveColorName = string.Empty;

            if (config.TryGetValue("passiveColor", ref passiveColorName))
            {
                Color32 color32;
                if (comp.TryGetNamedColor(passiveColorName, out color32))
                {
                    passiveColor = color32;
                }
                else
                {
                    string[] startColors = Utility.SplitVariableList(passiveColorName);
                    if (startColors.Length < 3 || startColors.Length > 4)
                    {
                        throw new ArgumentException("'passiveColor' does not contain 3 or 4 values in IMAGE " + name);
                    }

                    variableRegistrar.RegisterVariableChangeCallback(startColors[0], (double newValue) =>
                    {
                        passiveColor.r = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        UpdateColor();
                    });

                    variableRegistrar.RegisterVariableChangeCallback(startColors[1], (double newValue) =>
                    {
                        passiveColor.g = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        UpdateColor();
                    });

                    variableRegistrar.RegisterVariableChangeCallback(startColors[2], (double newValue) =>
                    {
                        passiveColor.b = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        UpdateColor();
                    });

                    if (startColors.Length == 4)
                    {
                        variableRegistrar.RegisterVariableChangeCallback(startColors[3], (double newValue) =>
                        {
                            passiveColor.a = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                            UpdateColor();
                        });
                    }
                }
            }

            string colorVariableName = string.Empty;

            if (config.TryGetValue("colorVariable", ref colorVariableName))
            {
                if (string.IsNullOrEmpty(passiveColorName))
                {
                    throw new ArgumentException("'colorVariable' found, but no 'passiveColor' in IMAGE " + name);
                }

                string activeColorName = string.Empty;
                if (!config.TryGetValue("activeColor", ref activeColorName))
                {
                    throw new ArgumentException("'colorVariable' found, but no 'activeColor' in IMAGE " + name);
                }

                string colorRangeString = string.Empty;
                if (config.TryGetValue("colorRange", ref colorRangeString))
                {
                    string[] colorRanges = Utility.SplitVariableList(colorRangeString);
                    if (colorRanges.Length != 2)
                    {
                        throw new ArgumentException("Expected 2 values for 'colorRange' in IMAGE " + name);
                    }

                    variableRegistrar.RegisterVariableChangeCallback(colorRanges[0], (double newValue) => colorRange1 = (float)newValue);
                    variableRegistrar.RegisterVariableChangeCallback(colorRanges[1], (double newValue) => colorRange2 = (float)newValue);

                    bool colorBlend = false;
                    if (config.TryGetValue("colorBlend", ref colorBlend) && colorBlend == true)
                    {
                        variableRegistrar.RegisterVariableChangeCallback(colorVariableName, (double newValue) =>
                        {
                            float newBlend = Mathf.InverseLerp(colorRange1, colorRange2, (float)newValue);

                            if (!Mathf.Approximately(newBlend, currentBlend))
                            {
                                currentBlend = newBlend;
                                UpdateColor();
                            }
                        });
                    }
                    else
                    {
                        variableRegistrar.RegisterVariableChangeCallback(colorVariableName, (double newValue) =>
                        {
                            float newBlend = (newValue.Between(colorRange1, colorRange2)) ? 1.0f : 0.0f;
                            if (newBlend != currentBlend)
                            {
                                currentBlend = newBlend;
                                UpdateColor();
                            }
                        });
                    }
                }
                else
                {
                    variableRegistrar.RegisterVariableChangeCallback(colorVariableName, (double newValue) =>
                    {
                        float newBlend = (newValue > 0.0) ? 1.0f : 0.0f;
                        if (newBlend != currentBlend)
                        {
                            currentBlend = newBlend;
                            UpdateColor();
                        }
                    });
                }

                Color32 color32;
                if (comp.TryGetNamedColor(activeColorName, out color32))
                {
                    activeColor = color32;
                }
                else
                {
                    string[] activeColors = Utility.SplitVariableList(activeColorName);
                    if (activeColors.Length < 3 || activeColors.Length > 4)
                    {
                        throw new ArgumentException("'activeColor' does not contain 3 or 4 values in IMAGE " + name);
                    }

                    variableRegistrar.RegisterVariableChangeCallback(activeColors[0], (double newValue) =>
                    {
                        activeColor.r = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        UpdateColor();
                    });

                    variableRegistrar.RegisterVariableChangeCallback(activeColors[1], (double newValue) =>
                    {
                        activeColor.g = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        UpdateColor();
                    });

                    variableRegistrar.RegisterVariableChangeCallback(activeColors[2], (double newValue) =>
                    {
                        activeColor.b = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                        UpdateColor();
                    });

                    if (activeColors.Length == 4)
                    {
                        variableRegistrar.RegisterVariableChangeCallback(activeColors[3], (double newValue) =>
                        {
                            activeColor.a = Mathf.Clamp01((float)newValue * (1.0f / 255.0f));
                            UpdateColor();
                        });
                    }
                }
            }

            // In case fixed colors are being used.
            UpdateColor();

            string uvTilingString = string.Empty;

            if (config.TryGetValue("tiling", ref uvTilingString))
            {
                string[] uvTile = Utility.SplitVariableList(uvTilingString);
                if (uvTile.Length != 2)
                {
                    throw new ArgumentException("'tiling' does not contain 2 values in IMAGE " + name);
                }

                variableRegistrar.RegisterVariableChangeCallback(uvTile[0], (double newValue) =>
                {
                    float rescale = Mathf.Max((float)newValue, 0.0f);
                    if (!Mathf.Approximately(rescale, uvScale.x))
                    {
                        uvScale.x = rescale;
                        imageMaterial.SetTextureScale("_MainTex", uvScale);
                    }
                });

                variableRegistrar.RegisterVariableChangeCallback(uvTile[1], (double newValue) =>
                {
                    float rescale = Mathf.Max((float)newValue, 0.0f);
                    if (!Mathf.Approximately(rescale, uvScale.y))
                    {
                        uvScale.y = rescale;
                        imageMaterial.SetTextureScale("_MainTex", uvScale);
                    }
                });
            }

            string uvShiftString = string.Empty;

            if (config.TryGetValue("uvShift", ref uvShiftString))
            {
                string[] uvShifting = Utility.SplitVariableList(uvShiftString);
                if (uvShifting.Length != 2)
                {
                    throw new ArgumentException("'uvShift' does not contain 2 values in IMAGE " + name);
                }

                variableRegistrar.RegisterVariableChangeCallback(uvShifting[0], (double newValue) =>
                {
                    float reshift = (float)newValue;

                    uvShift.x = reshift;
                    imageMaterial.SetTextureOffset("_MainTex", uvShift);
                });
                variableRegistrar.RegisterVariableChangeCallback(uvShifting[1], (double newValue) =>
                {
                    float reshift = (float)newValue;

                    uvShift.y = reshift;
                    imageMaterial.SetTextureOffset("_MainTex", uvShift);
                });
            }

            if (!string.IsNullOrEmpty(variableName))
            {
                // Disable the mesh if we're in variable mode
                imageObject.SetActive(false);
                variableRegistrar.RegisterVariableChangeCallback(variableName, VariableCallback);
            }
            else
            {
                imageObject.SetActive(true);
            }

            if (!string.IsNullOrEmpty(rotationVariableName))
            {
                variableRegistrar.RegisterVariableChangeCallback(rotationVariableName, RotationCallback);
            }
        }
Example #8
0
        internal MASPageRollingDigit(ConfigNode config, InternalProp prop, MASFlightComputer comp, MASMonitor monitor, Transform pageRoot, float depth)
            : base(config, prop, comp)
        {
            string localFonts = string.Empty;

            if (!config.TryGetValue("font", ref localFonts))
            {
                localFonts = string.Empty;
            }

            string styleStr = string.Empty;

            style = FontStyle.Normal;
            if (config.TryGetValue("style", ref styleStr))
            {
                style = MdVTextMesh.FontStyle(styleStr);
            }
            else
            {
                style = monitor.defaultStyle;
            }

            Vector2 fontDimensions = Vector2.zero;

            if (!config.TryGetValue("fontSize", ref fontDimensions) || fontDimensions.x < 0.0f || fontDimensions.y < 0.0f)
            {
                fontDimensions = monitor.fontSize;
            }

            Color32 textColor;
            string  textColorStr = string.Empty;

            if (!config.TryGetValue("textColor", ref textColorStr) || string.IsNullOrEmpty(textColorStr))
            {
                textColor = monitor.textColor_;
            }
            else
            {
                textColor = Utility.ParseColor32(textColorStr, comp);
            }

            // Position is based on default font size
            Vector2 fontScale = monitor.fontSize;

            string variableName = string.Empty;

            if (config.TryGetValue("variable", ref variableName))
            {
                variableName = variableName.Trim();
            }

            if (string.IsNullOrEmpty(localFonts))
            {
                font = monitor.defaultFont;
            }
            else
            {
                font = MASLoader.GetFont(localFonts.Trim());
            }

            // Set up our text.
            imageOrigin = pageRoot.position + new Vector3(monitor.screenSize.x * -0.5f, monitor.screenSize.y * 0.5f, depth);

            meshObject                    = new GameObject();
            meshObject.name               = Utility.ComposeObjectName(pageRoot.gameObject.name, this.GetType().Name, name, (int)(-depth / MASMonitor.depthDelta));
            meshObject.layer              = pageRoot.gameObject.layer;
            meshObject.transform.parent   = pageRoot;
            meshObject.transform.position = imageOrigin;

            meshFilter                        = meshObject.AddComponent <MeshFilter>();
            meshRenderer                      = meshObject.AddComponent <MeshRenderer>();
            meshRenderer.material             = new Material(MASLoader.shaders["MOARdV/TextMonitor"]);
            meshRenderer.material.mainTexture = font.material.mainTexture;

            string positionString = string.Empty;

            if (config.TryGetValue("position", ref positionString))
            {
                string[] positions = Utility.SplitVariableList(positionString);
                if (positions.Length != 2)
                {
                    throw new ArgumentException("position does not contain 2 values in ROLLING_DIGIT " + name);
                }

                variableRegistrar.RegisterVariableChangeCallback(positions[0], (double newValue) =>
                {
                    position.x = (float)newValue * fontScale.x;
                    meshObject.transform.position = imageOrigin + new Vector3(position.x, -position.y, 0.0f);
                });

                variableRegistrar.RegisterVariableChangeCallback(positions[1], (double newValue) =>
                {
                    position.y = (float)newValue * fontScale.y;
                    meshObject.transform.position = imageOrigin + new Vector3(position.x, -position.y, 0.0f);
                });
            }

            int maxDigits = 0;

            if (!config.TryGetValue("maxDigits", ref maxDigits))
            {
                throw new ArgumentException("'maxDigits' missing in ROLLING_DIGIT " + name);
            }

            if (!config.TryGetValue("numRolling", ref numRolling))
            {
                throw new ArgumentException("'numRolling' missing in ROLLING_DIGIT " + name);
            }
            if (numRolling < 1)
            {
                throw new ArgumentException("numRolling must be greater than zero in ROLLING_DIGIT " + name);
            }
            valueScalar = Mathf.Pow(10.0f, numRolling);
            numDigits   = maxDigits - numRolling;
            if (numDigits < 0)
            {
                throw new ArgumentException("'numRolling' must be less than 'maxDigits' in ROLLING_DIGIT " + name);
            }
            scrollingVerticesOffset = 4 * numDigits;

            upperLimit = Mathf.Pow(10.0f, maxDigits) - 1.0f;
            lowerLimit = -Mathf.Pow(10.0f, maxDigits - 1) + 1.0f;

            bool padZero = false;

            if (!config.TryGetValue("padZero", ref padZero))
            {
                padZero = false;
            }

            int numVertices = 4 * numDigits + 12 * numRolling;
            int numIndices  = 6 * numDigits + 18 * numRolling;

            vertices  = new Vector3[numVertices];
            colors32  = new Color32[numVertices];
            tangents  = new Vector4[numVertices];
            uv        = new Vector2[numVertices];
            triangles = new int[numIndices];
            // These values are invariant:
            Vector4 tangent = new Vector4(1.0f, 0.0f, 0.0f, 1.0f);

            for (int i = 0; i < numVertices; ++i)
            {
                colors32[i] = textColor;
                tangents[i] = tangent;
            }
            for (int i = 0; i < numIndices / 6; ++i)
            {
                triangles[i * 6 + 0] = i * 4 + 0;
                triangles[i * 6 + 1] = i * 4 + 3;
                triangles[i * 6 + 2] = i * 4 + 2;
                triangles[i * 6 + 3] = i * 4 + 0;
                triangles[i * 6 + 4] = i * 4 + 1;
                triangles[i * 6 + 5] = i * 4 + 3;
            }

            if (numDigits > 0)
            {
                StringBuilder sb = StringBuilderCache.Acquire();
                sb.AppendFormat("{{0,{0}:0", numDigits);
                if (padZero)
                {
                    sb.Append('0', numDigits - 1);
                }
                sb.Append("}");
                digitsFormat = sb.ToStringAndRelease();
            }

            string valueString = string.Empty;

            if (!config.TryGetValue("value", ref valueString))
            {
                throw new ArgumentException("'value' missing in ROLLING_DIGIT " + name);
            }
            variableRegistrar.RegisterVariableChangeCallback(valueString, ValueCallback);

            RenderPage(false);

            if (!string.IsNullOrEmpty(variableName))
            {
                // Disable the mesh if we're in variable mode
                meshObject.SetActive(false);
                variableRegistrar.RegisterVariableChangeCallback(variableName, VariableCallback);
            }
            else
            {
                currentState = true;
            }

            dynamic = font.dynamic;

            float characterScalar;

            if (dynamic)
            {
                characterScalar = fontDimensions.y / (float)font.lineHeight;
                this.fontSize   = font.fontSize;
            }
            else
            {
                // Unfortunately, there doesn't seem to be a way to set the font metrics when
                // creating a bitmap font, so I have to play games here by fetching the values
                // I stored in the character info.
                CharacterInfo ci = font.characterInfo[0];
                characterScalar = fontDimensions.y / (float)ci.glyphHeight;
                this.fontSize   = ci.glyphHeight;
            }

            this.fixedAdvance     = (int)fontDimensions.x;
            this.fixedLineSpacing = Mathf.Floor(fontDimensions.y);
            this.characterScalar  = characterScalar;
            ascent    = (dynamic) ? font.ascent : (int)(0.8125f * fixedLineSpacing);
            yMaxLimit = 0.5f * fixedLineSpacing;
            yMinLimit = -1.5f * fixedLineSpacing;

            Font.textureRebuilt += FontRebuiltCallback;
            font.RequestCharactersInTexture(charactersUsed, fontSize, style);

            digitsChanged   = true;
            fractionChanged = true;
        }
        internal MASPageMenu(ConfigNode config, InternalProp prop, MASFlightComputer comp, MASMonitor monitor, Transform pageRoot, float depth)
            : base(config, prop, comp)
        {
            this.prop = prop;
            this.comp = comp;

            if (!config.TryGetValue("maxLines", ref maxLines))
            {
                maxLines = int.MaxValue;
            }
            if (maxLines < 1)
            {
                throw new ArgumentException("'maxLines' must be greater than zero in MENU " + name);
            }

            int itemPositionShift = 0;

            if (!config.TryGetValue("itemPositionShift", ref itemPositionShift))
            {
                itemPositionShift = 0;
            }

            if (!config.TryGetValue("cursorPersistentName", ref cursorPersistentName))
            {
                throw new ArgumentException("Missing 'cursorPersistentName' in MENU " + name);
            }

            config.TryGetValue("upSoftkey", ref upSoftkey);
            config.TryGetValue("downSoftkey", ref downSoftkey);
            config.TryGetValue("enterSoftkey", ref enterSoftkey);
            config.TryGetValue("homeSoftkey", ref homeSoftkey);
            config.TryGetValue("endSoftkey", ref endSoftkey);

            string localFonts = string.Empty;

            if (!config.TryGetValue("font", ref localFonts))
            {
                localFonts = string.Empty;
            }

            string styleStr = string.Empty;

            style = FontStyle.Normal;
            if (config.TryGetValue("style", ref styleStr))
            {
                style = MdVTextMesh.FontStyle(styleStr);
            }
            else
            {
                style = monitor.defaultStyle;
            }

            fontSize = Vector2.zero;
            if (!config.TryGetValue("fontSize", ref fontSize) || fontSize.x < 0.0f || fontSize.y < 0.0f)
            {
                fontSize = monitor.fontSize;
            }

            charAdvance = fontSize.x * itemPositionShift;
            lineAdvance = fontSize.y;

            defaultColor = monitor.textColor_;

            Color32 cursorColor;
            string  cursorColorStr = string.Empty;

            if (!config.TryGetValue("cursorColor", ref cursorColorStr) || string.IsNullOrEmpty(cursorColorStr))
            {
                cursorColor = monitor.textColor_;
            }
            else
            {
                cursorColor = Utility.ParseColor32(cursorColorStr, comp);
            }

            // Set up our text.
            textOrigin = pageRoot.position + new Vector3(monitor.screenSize.x * -0.5f, monitor.screenSize.y * 0.5f, depth);

            rootObject                    = new GameObject();
            rootObject.name               = Utility.ComposeObjectName(pageRoot.gameObject.name, this.GetType().Name, name, (int)(-depth / MASMonitor.depthDelta));
            rootObject.layer              = pageRoot.gameObject.layer;
            rootObject.transform.parent   = pageRoot;
            rootObject.transform.position = textOrigin;

            string positionString = string.Empty;

            if (config.TryGetValue("position", ref positionString))
            {
                string[] positions = Utility.SplitVariableList(positionString);
                if (positions.Length != 2)
                {
                    throw new ArgumentException("position does not contain 2 values in MENU " + name);
                }

                variableRegistrar.RegisterVariableChangeCallback(positions[0], (double newValue) =>
                {
                    position.x = (float)newValue * monitor.fontSize.x;
                    rootObject.transform.position = textOrigin + new Vector3(position.x, -position.y, 0.0f);
                    updateMenu = true;
                });

                variableRegistrar.RegisterVariableChangeCallback(positions[1], (double newValue) =>
                {
                    position.y = (float)newValue * monitor.fontSize.y;
                    rootObject.transform.position = textOrigin + new Vector3(position.x, -position.y, 0.0f);
                    updateMenu = true;
                });
            }

            if (string.IsNullOrEmpty(localFonts))
            {
                font = monitor.defaultFont;
            }
            else
            {
                font = MASLoader.GetFont(localFonts.Trim());
            }

            cursorObject                    = new GameObject();
            cursorObject.name               = rootObject.name + "_cursor";
            cursorObject.layer              = rootObject.layer;
            cursorObject.transform.parent   = rootObject.transform;
            cursorObject.transform.position = textOrigin;

            cursorText          = cursorObject.AddComponent <MdVTextMesh>();
            cursorText.material = new Material(MASLoader.shaders["MOARdV/TextMonitor"]);
            cursorText.SetFont(font, fontSize);
            cursorText.SetColor(cursorColor);
            cursorText.material.SetFloat(Shader.PropertyToID("_EmissiveFactor"), 1.0f);
            cursorText.fontStyle = style;

            string cursorPrompt = string.Empty;

            config.TryGetValue("cursor", ref cursorPrompt);
            // text, immutable, preserveWhitespace, comp, prop
            cursorText.SetText(cursorPrompt, false, true, comp, prop);

            string itemCountStr = string.Empty;

            config.TryGetValue("itemCount", ref itemCountStr);

            List <MenuItem> itemNodes = new List <MenuItem>();

            ConfigNode[] menuItemConfigNodes = config.GetNodes("ITEM");
            foreach (ConfigNode itemNode in menuItemConfigNodes)
            {
                try
                {
                    MenuItem cpt = new MenuItem(itemNode, rootObject, font, fontSize, style, defaultColor, comp, prop, variableRegistrar, itemNodes.Count);
                    itemNodes.Add(cpt);
                }
                catch (Exception e)
                {
                    Utility.LogError(this, "Exception creating ITEM in MENU " + name);
                    Utility.LogError(this, e.ToString());
                }
            }
            if (itemNodes.Count == 0)
            {
                throw new ArgumentException("No valid ITEM nodes in MENU " + name);
            }
            menuItems = itemNodes.ToArray();
            if (string.IsNullOrEmpty(itemCountStr))
            {
                numMenuItems = menuItems.Length;

                softkeyUpAction   = comp.GetAction(string.Format("fc.AddPersistentWrapped(\"{0}\", -1, 0, {1})", cursorPersistentName, numMenuItems), prop);
                softkeyDownAction = comp.GetAction(string.Format("fc.AddPersistentWrapped(\"{0}\", 1, 0, {1})", cursorPersistentName, numMenuItems), prop);
                softkeyEndAction  = comp.GetAction(string.Format("fc.SetPersistent(\"{0}\", {1})", cursorPersistentName, numMenuItems - 1), prop);
            }
            else if (itemNodes.Count > 1)
            {
                throw new ArgumentException("Only one valid ITEM node may be used in dynamic MENU " + name);
            }
            else
            {
                dynamicMenuTemplate = menuItemConfigNodes[0].CreateCopy();
            }

            variableRegistrar.RegisterVariableChangeCallback(string.Format("fc.GetPersistentAsNumber(\"{0}\")", cursorPersistentName), CursorMovedCallback);
            softkeyHomeAction = comp.GetAction(string.Format("fc.SetPersistent(\"{0}\", 0)", cursorPersistentName), prop);

            if (!string.IsNullOrEmpty(itemCountStr))
            {
                variableRegistrar.RegisterVariableChangeCallback(itemCountStr, MenuCountCallback);
            }

            string masterVariableName = string.Empty;

            if (config.TryGetValue("variable", ref masterVariableName))
            {
                rootObject.SetActive(false);

                variableRegistrar.RegisterVariableChangeCallback(masterVariableName, VariableCallback);
            }
            else
            {
                currentState = true;
                rootObject.SetActive(true);
            }

            RenderPage(false);
        }