public VariableAnimationSet(ConfigNode node, InternalProp thisProp)
        {
            part = thisProp.part;

            if (!node.HasData)
            {
                throw new ArgumentException("No data?!");
            }

            comp = RasterPropMonitorComputer.Instantiate(thisProp);

            string[] tokens = { };

            if (node.HasValue("scale"))
            {
                tokens = node.GetValue("scale").Split(',');
            }

            if (tokens.Length != 2)
            {
                throw new ArgumentException("Could not parse 'scale' parameter.");
            }

            if (node.HasValue("variableName"))
            {
                string variableName;
                variableName = node.GetValue("variableName").Trim();
                scaleEnds[2] = new VariableOrNumber(variableName, this);
            }
            else if (node.HasValue("stateMethod"))
            {
                Func<bool> stateFunction = (Func<bool>)comp.GetMethod(node.GetValue("stateMethod").Trim(), thisProp, typeof(Func<bool>));
                if (stateFunction != null)
                {
                    scaleEnds[2] = new VariableOrNumber(stateFunction, this);
                }
                else
                {
                    throw new ArgumentException("Unrecognized stateMethod");
                }
            }
            else
            {
                throw new ArgumentException("Missing variable name.");
            }

            scaleEnds[0] = new VariableOrNumber(tokens[0], this);
            scaleEnds[1] = new VariableOrNumber(tokens[1], this);

            // That takes care of the scale, now what to do about that scale:

            if (node.HasValue("reverse"))
            {
                if (!bool.TryParse(node.GetValue("reverse"), out reverse))
                {
                    throw new ArgumentException("So is 'reverse' true or false?");
                }
            }

            if (node.HasValue("animationName"))
            {
                animationName = node.GetValue("animationName");
                if (node.HasValue("animationSpeed"))
                {
                    animationSpeed = float.Parse(node.GetValue("animationSpeed"));

                    if (reverse)
                    {
                        animationSpeed = -animationSpeed;
                    }
                }
                else
                {
                    animationSpeed = 0.0f;
                }
                Animation[] anims = node.HasValue("animateExterior") ? thisProp.part.FindModelAnimators(animationName) : thisProp.FindModelAnimators(animationName);
                if (anims.Length > 0)
                {
                    onAnim = anims[0];
                    onAnim.enabled = true;
                    onAnim[animationName].speed = 0;
                    onAnim[animationName].normalizedTime = reverse ? 1f : 0f;
                    looping = node.HasValue("loopingAnimation");
                    if (looping)
                    {
                        onAnim[animationName].wrapMode = WrapMode.Loop;
                        onAnim.wrapMode = WrapMode.Loop;
                        onAnim[animationName].speed = animationSpeed;
                        mode = Mode.LoopingAnimation;
                    }
                    else
                    {
                        onAnim[animationName].wrapMode = WrapMode.Once;
                        mode = Mode.Animation;
                    }
                    onAnim.Play();
                    alwaysActive = node.HasValue("animateExterior");
                }
                else
                {
                    throw new ArgumentException("Animation could not be found.");
                }

                if (node.HasValue("stopAnimationName"))
                {
                    stopAnimationName = node.GetValue("stopAnimationName");
                    anims = node.HasValue("animateExterior") ? thisProp.part.FindModelAnimators(stopAnimationName) : thisProp.FindModelAnimators(stopAnimationName);
                    if (anims.Length > 0)
                    {
                        offAnim = anims[0];
                        offAnim.enabled = true;
                        offAnim[stopAnimationName].speed = 0;
                        offAnim[stopAnimationName].normalizedTime = reverse ? 1f : 0f;
                        if (looping)
                        {
                            offAnim[stopAnimationName].wrapMode = WrapMode.Loop;
                            offAnim.wrapMode = WrapMode.Loop;
                            offAnim[stopAnimationName].speed = animationSpeed;
                            mode = Mode.LoopingAnimation;
                        }
                        else
                        {
                            offAnim[stopAnimationName].wrapMode = WrapMode.Once;
                            mode = Mode.Animation;
                        }
                    }
                }
            }
            else if (node.HasValue("activeColor") && node.HasValue("passiveColor") && node.HasValue("coloredObject"))
            {
                if (node.HasValue("colorName"))
                    colorName = node.GetValue("colorName");
                passiveColor = ConfigNode.ParseColor32(node.GetValue("passiveColor"));
                activeColor = ConfigNode.ParseColor32(node.GetValue("activeColor"));
                colorShiftRenderer = thisProp.FindModelComponent<Renderer>(node.GetValue("coloredObject"));
                colorShiftRenderer.material.SetColor(colorName, reverse ? activeColor : passiveColor);
                mode = Mode.Color;
            }
            else if (node.HasValue("controlledTransform") && node.HasValue("localRotationStart") && node.HasValue("localRotationEnd"))
            {
                controlledTransform = thisProp.FindModelTransform(node.GetValue("controlledTransform").Trim());
                initialRotation = controlledTransform.localRotation;
                if (node.HasValue("longPath"))
                {
                    longPath = true;
                    vectorStart = ConfigNode.ParseVector3(node.GetValue("localRotationStart"));
                    vectorEnd = ConfigNode.ParseVector3(node.GetValue("localRotationEnd"));
                }
                else
                {
                    rotationStart = Quaternion.Euler(ConfigNode.ParseVector3(node.GetValue("localRotationStart")));
                    rotationEnd = Quaternion.Euler(ConfigNode.ParseVector3(node.GetValue("localRotationEnd")));
                }
                mode = Mode.Rotation;
            }
            else if (node.HasValue("controlledTransform") && node.HasValue("localTranslationStart") && node.HasValue("localTranslationEnd"))
            {
                controlledTransform = thisProp.FindModelTransform(node.GetValue("controlledTransform").Trim());
                initialPosition = controlledTransform.localPosition;
                vectorStart = ConfigNode.ParseVector3(node.GetValue("localTranslationStart"));
                vectorEnd = ConfigNode.ParseVector3(node.GetValue("localTranslationEnd"));
                mode = Mode.Translation;
            }
            else if (node.HasValue("controlledTransform") && node.HasValue("localScaleStart") && node.HasValue("localScaleEnd"))
            {
                controlledTransform = thisProp.FindModelTransform(node.GetValue("controlledTransform").Trim());
                initialScale = controlledTransform.localScale;
                vectorStart = ConfigNode.ParseVector3(node.GetValue("localScaleStart"));
                vectorEnd = ConfigNode.ParseVector3(node.GetValue("localScaleEnd"));
                mode = Mode.Scale;
            }
            else if (node.HasValue("controlledTransform") && node.HasValue("textureLayers") && node.HasValue("textureShiftStart") && node.HasValue("textureShiftEnd"))
            {
                affectedMaterial = thisProp.FindModelTransform(node.GetValue("controlledTransform").Trim()).renderer.material;
                textureLayer = node.GetValue("textureLayers");
                textureShiftStart = ConfigNode.ParseVector2(node.GetValue("textureShiftStart"));
                textureShiftEnd = ConfigNode.ParseVector2(node.GetValue("textureShiftEnd"));
                mode = Mode.TextureShift;
            }
            else if (node.HasValue("controlledTransform") && node.HasValue("textureLayers") && node.HasValue("textureScaleStart") && node.HasValue("textureScaleEnd"))
            {
                affectedMaterial = thisProp.FindModelTransform(node.GetValue("controlledTransform").Trim()).renderer.material;
                textureLayer = node.GetValue("textureLayers");
                textureScaleStart = ConfigNode.ParseVector2(node.GetValue("textureScaleStart"));
                textureScaleEnd = ConfigNode.ParseVector2(node.GetValue("textureScaleEnd"));
                mode = Mode.TextureScale;
            }
            else
            {
                throw new ArgumentException("Cannot initiate any of the possible action modes.");
            }

            if (node.HasValue("threshold"))
            {
                threshold = ConfigNode.ParseVector2(node.GetValue("threshold"));
            }

            resourceAmount = 0.0f;
            if (threshold != Vector2.zero)
            {
                thresholdMode = true;

                float min = Mathf.Min(threshold.x, threshold.y);
                float max = Mathf.Max(threshold.x, threshold.y);
                threshold.x = min;
                threshold.y = max;

                if (node.HasValue("flashingDelay"))
                {
                    flashingDelay = double.Parse(node.GetValue("flashingDelay"));
                }

                if (node.HasValue("alarmSound"))
                {
                    alarmSoundVolume = 0.5f;
                    if (node.HasValue("alarmSoundVolume"))
                        alarmSoundVolume = float.Parse(node.GetValue("alarmSoundVolume"));
                    audioOutput = JUtil.SetupIVASound(thisProp, node.GetValue("alarmSound"), alarmSoundVolume, false);
                    if (node.HasValue("alarmMustPlayOnce"))
                    {
                        if (!bool.TryParse(node.GetValue("alarmMustPlayOnce"), out alarmMustPlayOnce))
                            throw new ArgumentException("So is 'alarmMustPlayOnce' true or false?");
                    }
                    if (node.HasValue("alarmShutdownButton"))
                        SmarterButton.CreateButton(thisProp, node.GetValue("alarmShutdownButton"), AlarmShutdown);
                    if (node.HasValue("alarmSoundLooping"))
                    {
                        if (!bool.TryParse(node.GetValue("alarmSoundLooping"), out alarmSoundLooping))
                            throw new ArgumentException("So is 'alarmSoundLooping' true or false?");
                        audioOutput.audio.loop = alarmSoundLooping;
                    }
                }

                if (node.HasValue("resourceAmount"))
                {
                    resourceAmount = float.Parse(node.GetValue("resourceAmount"));
                }

                TurnOff();
            }
        }
        public VariableAnimationSet(ConfigNode node, InternalProp thisProp, RasterPropMonitorComputer rpmComp, JSIVariableAnimator parent)
        {
            varAnim = parent;
            onChangeDelegate = (Action<float>)Delegate.CreateDelegate(typeof(Action<float>), this, "OnChange");
            part = thisProp.part;

            if (!node.HasData)
            {
                throw new ArgumentException("No data?!");
            }

            string[] tokens = { };

            if (node.HasValue("scale"))
            {
                tokens = node.GetValue("scale").Split(',');
            }

            if (tokens.Length != 2)
            {
                throw new ArgumentException("Could not parse 'scale' parameter.");
            }

            string variableName = string.Empty;
            if (node.HasValue("variableName"))
            {
                variableName = node.GetValue("variableName").Trim();
            }
            else if (node.HasValue("stateMethod"))
            {
                string stateMethod = node.GetValue("stateMethod").Trim();
                // Verify the state method actually exists
                Func<bool> stateFunction = (Func<bool>)rpmComp.GetMethod(stateMethod, thisProp, typeof(Func<bool>));
                if (stateFunction != null)
                {
                    variableName = "PLUGIN_" + stateMethod;
                }
                else
                {
                    throw new ArgumentException("Unrecognized stateMethod");
                }
            }
            else
            {
                throw new ArgumentException("Missing variable name.");
            }

            if (node.HasValue("modulo"))
            {
                variable = new VariableOrNumberRange(rpmComp, variableName, tokens[0], tokens[1], node.GetValue("modulo"));
                usesModulo = true;
            }
            else
            {
                variable = new VariableOrNumberRange(rpmComp, variableName, tokens[0], tokens[1]);
                usesModulo = false;
            }

            // That takes care of the scale, now what to do about that scale:
            if (node.HasValue("reverse"))
            {
                if (!bool.TryParse(node.GetValue("reverse"), out reverse))
                {
                    throw new ArgumentException("So is 'reverse' true or false?");
                }
            }

            if (node.HasValue("animationName"))
            {
                animationName = node.GetValue("animationName");
                if (node.HasValue("animationSpeed"))
                {
                    animationSpeed = float.Parse(node.GetValue("animationSpeed"));

                    if (reverse)
                    {
                        animationSpeed = -animationSpeed;
                    }
                }
                else
                {
                    animationSpeed = 0.0f;
                }
                Animation[] anims = node.HasValue("animateExterior") ? thisProp.part.FindModelAnimators(animationName) : thisProp.FindModelAnimators(animationName);
                if (anims.Length > 0)
                {
                    onAnim = anims[0];
                    onAnim.enabled = true;
                    onAnim[animationName].speed = 0;
                    onAnim[animationName].normalizedTime = reverse ? 1f : 0f;
                    looping = node.HasValue("loopingAnimation");
                    if (looping)
                    {
                        onAnim[animationName].wrapMode = WrapMode.Loop;
                        onAnim.wrapMode = WrapMode.Loop;
                        onAnim[animationName].speed = animationSpeed;
                        mode = Mode.LoopingAnimation;
                    }
                    else
                    {
                        onAnim[animationName].wrapMode = WrapMode.Once;
                        mode = Mode.Animation;
                    }
                    onAnim.Play();
                    alwaysActive = node.HasValue("animateExterior");
                }
                else
                {
                    throw new ArgumentException("Animation could not be found.");
                }

                if (node.HasValue("stopAnimationName"))
                {
                    stopAnimationName = node.GetValue("stopAnimationName");
                    anims = node.HasValue("animateExterior") ? thisProp.part.FindModelAnimators(stopAnimationName) : thisProp.FindModelAnimators(stopAnimationName);
                    if (anims.Length > 0)
                    {
                        offAnim = anims[0];
                        offAnim.enabled = true;
                        offAnim[stopAnimationName].speed = 0;
                        offAnim[stopAnimationName].normalizedTime = reverse ? 1f : 0f;
                        if (looping)
                        {
                            offAnim[stopAnimationName].wrapMode = WrapMode.Loop;
                            offAnim.wrapMode = WrapMode.Loop;
                            offAnim[stopAnimationName].speed = animationSpeed;
                            mode = Mode.LoopingAnimation;
                        }
                        else
                        {
                            offAnim[stopAnimationName].wrapMode = WrapMode.Once;
                            mode = Mode.Animation;
                        }
                    }
                }
            }
            else if (node.HasValue("activeColor") && node.HasValue("passiveColor") && node.HasValue("coloredObject"))
            {
                string colorNameString = "_EmissiveColor";
                if (node.HasValue("colorName"))
                {
                    colorNameString = node.GetValue("colorName");
                }
                colorName = Shader.PropertyToID(colorNameString);

                if (reverse)
                {
                    activeColor = JUtil.ParseColor32(node.GetValue("passiveColor"), thisProp.part, ref rpmComp);
                    passiveColor = JUtil.ParseColor32(node.GetValue("activeColor"), thisProp.part, ref rpmComp);
                }
                else
                {
                    passiveColor = JUtil.ParseColor32(node.GetValue("passiveColor"), thisProp.part, ref rpmComp);
                    activeColor = JUtil.ParseColor32(node.GetValue("activeColor"), thisProp.part, ref rpmComp);
                }
                Renderer colorShiftRenderer = thisProp.FindModelComponent<Renderer>(node.GetValue("coloredObject"));
                affectedMaterial = colorShiftRenderer.material;
                affectedMaterial.SetColor(colorName, passiveColor);
                mode = Mode.Color;
            }
            else if (node.HasValue("controlledTransform") && node.HasValue("localRotationStart") && node.HasValue("localRotationEnd"))
            {
                controlledTransform = thisProp.FindModelTransform(node.GetValue("controlledTransform").Trim());
                initialRotation = controlledTransform.localRotation;
                if (node.HasValue("longPath"))
                {
                    longPath = true;
                    if (reverse)
                    {
                        vectorEnd = ConfigNode.ParseVector3(node.GetValue("localRotationStart"));
                        vectorStart = ConfigNode.ParseVector3(node.GetValue("localRotationEnd"));
                    }
                    else
                    {
                        vectorStart = ConfigNode.ParseVector3(node.GetValue("localRotationStart"));
                        vectorEnd = ConfigNode.ParseVector3(node.GetValue("localRotationEnd"));
                    }
                }
                else
                {
                    if (reverse)
                    {
                        rotationEnd = Quaternion.Euler(ConfigNode.ParseVector3(node.GetValue("localRotationStart")));
                        rotationStart = Quaternion.Euler(ConfigNode.ParseVector3(node.GetValue("localRotationEnd")));
                    }
                    else
                    {
                        rotationStart = Quaternion.Euler(ConfigNode.ParseVector3(node.GetValue("localRotationStart")));
                        rotationEnd = Quaternion.Euler(ConfigNode.ParseVector3(node.GetValue("localRotationEnd")));
                    }
                }
                mode = Mode.Rotation;
            }
            else if (node.HasValue("controlledTransform") && node.HasValue("localTranslationStart") && node.HasValue("localTranslationEnd"))
            {
                controlledTransform = thisProp.FindModelTransform(node.GetValue("controlledTransform").Trim());
                initialPosition = controlledTransform.localPosition;
                if (reverse)
                {
                    vectorEnd = ConfigNode.ParseVector3(node.GetValue("localTranslationStart"));
                    vectorStart = ConfigNode.ParseVector3(node.GetValue("localTranslationEnd"));
                }
                else
                {
                    vectorStart = ConfigNode.ParseVector3(node.GetValue("localTranslationStart"));
                    vectorEnd = ConfigNode.ParseVector3(node.GetValue("localTranslationEnd"));
                }
                mode = Mode.Translation;
            }
            else if (node.HasValue("controlledTransform") && node.HasValue("localScaleStart") && node.HasValue("localScaleEnd"))
            {
                controlledTransform = thisProp.FindModelTransform(node.GetValue("controlledTransform").Trim());
                initialScale = controlledTransform.localScale;
                if (reverse)
                {
                    vectorEnd = ConfigNode.ParseVector3(node.GetValue("localScaleStart"));
                    vectorStart = ConfigNode.ParseVector3(node.GetValue("localScaleEnd"));
                }
                else
                {
                    vectorStart = ConfigNode.ParseVector3(node.GetValue("localScaleStart"));
                    vectorEnd = ConfigNode.ParseVector3(node.GetValue("localScaleEnd"));
                }
                mode = Mode.Scale;
            }
            else if (node.HasValue("controlledTransform") && node.HasValue("textureLayers") && node.HasValue("textureShiftStart") && node.HasValue("textureShiftEnd"))
            {
                affectedMaterial = thisProp.FindModelTransform(node.GetValue("controlledTransform").Trim()).GetComponent<Renderer>().material;
                var textureLayers = node.GetValue("textureLayers").Split(',');
                for (int i = 0; i < textureLayers.Length; ++i)
                {
                    textureLayer.Add(textureLayers[i].Trim());
                }

                if (reverse)
                {
                    textureShiftEnd = ConfigNode.ParseVector2(node.GetValue("textureShiftStart"));
                    textureShiftStart = ConfigNode.ParseVector2(node.GetValue("textureShiftEnd"));
                }
                else
                {
                    textureShiftStart = ConfigNode.ParseVector2(node.GetValue("textureShiftStart"));
                    textureShiftEnd = ConfigNode.ParseVector2(node.GetValue("textureShiftEnd"));
                }
                mode = Mode.TextureShift;
            }
            else if (node.HasValue("controlledTransform") && node.HasValue("textureLayers") && node.HasValue("textureScaleStart") && node.HasValue("textureScaleEnd"))
            {
                affectedMaterial = thisProp.FindModelTransform(node.GetValue("controlledTransform").Trim()).GetComponent<Renderer>().material;
                var textureLayers = node.GetValue("textureLayers").Split(',');
                for (int i = 0; i < textureLayers.Length; ++i)
                {
                    textureLayer.Add(textureLayers[i].Trim());
                }

                if (reverse)
                {
                    textureScaleEnd = ConfigNode.ParseVector2(node.GetValue("textureScaleStart"));
                    textureScaleStart = ConfigNode.ParseVector2(node.GetValue("textureScaleEnd"));
                }
                else
                {
                    textureScaleStart = ConfigNode.ParseVector2(node.GetValue("textureScaleStart"));
                    textureScaleEnd = ConfigNode.ParseVector2(node.GetValue("textureScaleEnd"));
                }
                mode = Mode.TextureScale;
            }
            else
            {
                throw new ArgumentException("Cannot initiate any of the possible action modes.");
            }

            if (!(node.HasValue("maxRateChange") && float.TryParse(node.GetValue("maxRateChange"), out maxRateChange)))
            {
                maxRateChange = 0.0f;
            }
            if (maxRateChange >= 60.0f)
            {
                // Animation rate is too fast to even notice @60Hz
                maxRateChange = 0.0f;
            }
            else
            {
                lastAnimUpdate = Planetarium.GetUniversalTime();
            }

            if (node.HasValue("threshold"))
            {
                threshold = ConfigNode.ParseVector2(node.GetValue("threshold"));
            }

            resourceAmount = 0.0f;
            if (threshold != Vector2.zero)
            {
                thresholdMode = true;

                float min = Mathf.Min(threshold.x, threshold.y);
                float max = Mathf.Max(threshold.x, threshold.y);
                threshold.x = min;
                threshold.y = max;

                if (node.HasValue("flashingDelay"))
                {
                    flashingDelay = double.Parse(node.GetValue("flashingDelay"));
                }

                if (node.HasValue("alarmSound"))
                {
                    alarmSoundVolume = 0.5f;
                    if (node.HasValue("alarmSoundVolume"))
                    {
                        alarmSoundVolume = float.Parse(node.GetValue("alarmSoundVolume"));
                    }
                    audioOutput = JUtil.SetupIVASound(thisProp, node.GetValue("alarmSound"), alarmSoundVolume, false);
                    if (node.HasValue("alarmMustPlayOnce"))
                    {
                        if (!bool.TryParse(node.GetValue("alarmMustPlayOnce"), out alarmMustPlayOnce))
                        {
                            throw new ArgumentException("So is 'alarmMustPlayOnce' true or false?");
                        }
                    }
                    if (node.HasValue("alarmShutdownButton"))
                    {
                        SmarterButton.CreateButton(thisProp, node.GetValue("alarmShutdownButton"), AlarmShutdown);
                    }
                    if (node.HasValue("alarmSoundLooping"))
                    {
                        if (!bool.TryParse(node.GetValue("alarmSoundLooping"), out alarmSoundLooping))
                        {
                            throw new ArgumentException("So is 'alarmSoundLooping' true or false?");
                        }
                        audioOutput.audio.loop = alarmSoundLooping;
                    }

                    inIVA = (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA);

                    GameEvents.OnCameraChange.Add(OnCameraChange);
                }

                if (node.HasValue("resourceAmount"))
                {
                    resourceAmount = float.Parse(node.GetValue("resourceAmount"));

                    if (node.HasValue("resourceName"))
                    {
                        resourceName = node.GetValue("resourceName");
                    }
                    else
                    {
                        resourceName = "ElectricCharge";
                    }
                }

                TurnOff(Planetarium.GetUniversalTime());
            }

            rpmComp.RegisterVariableCallback(variable.variableName, onChangeDelegate);
        }
        public void Start()
        {
            if (HighLogic.LoadedSceneIsEditor)
                return;

            try
            {

                if (!groupList.ContainsKey(actionName) && !customGroupList.ContainsKey(actionName))
                {
                    JUtil.LogErrorMessage(this, "Action \"{0}\" is not supported.", actionName);
                    return;
                }

                // Parse the needs-electric-charge here.
                if (!string.IsNullOrEmpty(needsElectricCharge))
                {
                    switch (needsElectricCharge.ToLowerInvariant().Trim())
                    {
                        case "true":
                        case "yes":
                        case "1":
                            needsElectricChargeValue = true;
                            break;
                        case "false":
                        case "no":
                        case "0":
                            needsElectricChargeValue = false;
                            break;
                    }
                }

                // Now parse consumeOnToggle and consumeWhileActive...
                if (!string.IsNullOrEmpty(consumeOnToggle))
                {
                    string[] tokens = consumeOnToggle.Split(',');
                    if (tokens.Length == 3)
                    {
                        consumeOnToggleName = tokens[0].Trim();
                        if (!(PartResourceLibrary.Instance.GetDefinition(consumeOnToggleName) != null &&
                           float.TryParse(tokens[1].Trim(), NumberStyles.Any, CultureInfo.InvariantCulture,
                               out consumeOnToggleAmount)))
                        {
                            JUtil.LogErrorMessage(this, "Could not parse \"{0}\"", consumeOnToggle);
                        }
                        switch (tokens[2].Trim().ToLower())
                        {
                            case "on":
                                consumingOnToggleUp = true;
                                break;
                            case "off":
                                consumingOnToggleDown = true;
                                break;
                            case "both":
                                consumingOnToggleUp = true;
                                consumingOnToggleDown = true;
                                break;
                            default:
                                JUtil.LogErrorMessage(this, "So should I consume resources when turning on, turning off, or both in \"{0}\"?", consumeOnToggle);
                                break;
                        }
                    }
                }

                if (!string.IsNullOrEmpty(consumeWhileActive))
                {
                    string[] tokens = consumeWhileActive.Split(',');
                    if (tokens.Length == 2)
                    {
                        consumeWhileActiveName = tokens[0].Trim();
                        if (!(PartResourceLibrary.Instance.GetDefinition(consumeWhileActiveName) != null &&
                           float.TryParse(tokens[1].Trim(),
                               NumberStyles.Any, CultureInfo.InvariantCulture,
                               out consumeWhileActiveAmount)))
                        {
                            JUtil.LogErrorMessage(this, "Could not parse \"{0}\"", consumeWhileActive);
                        }
                        else
                        {
                            consumingWhileActive = true;
                            JUtil.LogMessage(this, "Switch in prop {0} prop id {1} will consume {2} while active at a rate of {3}", internalProp.propName,
                                internalProp.propID, consumeWhileActiveName, consumeWhileActiveAmount);
                        }
                    }
                }

                if (groupList.ContainsKey(actionName))
                {
                    currentState = vessel.ActionGroups[groupList[actionName]];
                    // action group switches may not belong to a radio group
                    switchGroupIdentifier = -1;
                }
                else
                {
                    isCustomAction = true;
                    switch (actionName)
                    {
                        case "intlight":
                            persistentVarName = internalLightName;
                            lightObjects = internalModel.FindModelComponents<Light>();
                            needsElectricChargeValue |= string.IsNullOrEmpty(needsElectricCharge) || needsElectricChargeValue;
                            break;
                        case "plugin":
                            persistentVarName = string.Empty;
                            comp = RasterPropMonitorComputer.Instantiate(internalProp);
                            comp.UpdateRefreshRates(lightCheckRate, lightCheckRate);

                            foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes("PROP"))
                            {
                                if (node.GetValue("name") == internalProp.propName)
                                {
                                    foreach (ConfigNode pluginConfig in node.GetNodes("MODULE")[moduleID].GetNodes("PLUGINACTION"))
                                    {
                                        if (pluginConfig.HasValue("name") && pluginConfig.HasValue("actionMethod"))
                                        {
                                            string action = pluginConfig.GetValue("name").Trim() + ":" + pluginConfig.GetValue("actionMethod").Trim();
                                            actionHandler = (Action<bool>)comp.GetMethod(action, internalProp, typeof(Action<bool>));

                                            if (actionHandler == null)
                                            {
                                                JUtil.LogErrorMessage(this, "Failed to instantiate action handler {0}", pluginConfig.GetValue("name"));
                                            }
                                            else
                                            {
                                                if (pluginConfig.HasValue("stateMethod"))
                                                {
                                                    string state = pluginConfig.GetValue("name").Trim() + ":" + pluginConfig.GetValue("stateMethod").Trim();
                                                    stateHandler = (Func<bool>)comp.GetMethod(state, internalProp, typeof(Func<bool>));
                                                }
                                                isPluginAction = true;
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                            if (actionHandler == null)
                            {
                                actionName = "dummy";
                                JUtil.LogMessage(this, "Plugin handlers did not start, reverting to dummy mode.");
                            }
                            break;
                        default:
                            persistentVarName = "switch" + internalProp.propID + "_" + moduleID;
                            break;
                    }
                    if (!string.IsNullOrEmpty(perPodPersistenceName))
                    {
                        persistentVarName = perPodPersistenceName;
                    }
                    else
                    {
                        // If there's no persistence name, there's no valid group id for this switch
                        switchGroupIdentifier = -1;
                    }
                }

                if (needsElectricChargeValue || !string.IsNullOrEmpty(persistentVarName) || !string.IsNullOrEmpty(perPodMasterSwitchName) || !string.IsNullOrEmpty(masterVariableName))
                {
                    if (comp == null)
                    {
                        comp = RasterPropMonitorComputer.Instantiate(internalProp);
                        comp.UpdateRefreshRates(lightCheckRate, lightCheckRate);
                    }

                    if (!string.IsNullOrEmpty(masterVariableName))
                    {
                        masterVariable = new VariableOrNumber(masterVariableName, this);
                        string[] range = masterVariableRange.Split(',');
                        if(range.Length == 2)
                        {
                            masterRange[0] = new VariableOrNumber(range[0], this);
                            masterRange[1] = new VariableOrNumber(range[1], this);
                        }
                        else
                        {
                            masterVariable = null;
                        }
                    }
                }

                // set up the toggle switch
                if (!string.IsNullOrEmpty(switchTransform))
                {
                    SmarterButton.CreateButton(internalProp, switchTransform, Click);
                }

                if (isCustomAction)
                {
                    if (isPluginAction && stateHandler != null)
                    {
                        currentState = stateHandler();
                    }
                    else
                    {
                        if (!string.IsNullOrEmpty(persistentVarName))
                        {
                            if (switchGroupIdentifier >= 0)
                            {
                                int activeSwitch = comp.Persistence.GetVar(persistentVarName, 0);

                                currentState = customGroupList[actionName] = (switchGroupIdentifier == activeSwitch);
                            }
                            else
                            {
                                currentState = customGroupList[actionName] = comp.Persistence.GetBool(persistentVarName, initialState);
                            }

                            if (actionName == "intlight")
                            {
                                // We have to restore lighting after reading the
                                // persistent variable.
                                SetInternalLights(customGroupList[actionName]);
                            }
                        }
                    }
                }

                if (!string.IsNullOrEmpty(persistentVarName) && !comp.Persistence.HasVar(persistentVarName))
                {
                    if (switchGroupIdentifier >= 0)
                    {
                        if (currentState)
                        {
                            comp.Persistence.SetVar(persistentVarName, switchGroupIdentifier);
                        }
                    }
                    else
                    {
                        comp.Persistence.SetVar(persistentVarName, currentState);
                    }
                }

                if (!string.IsNullOrEmpty(animationName))
                {
                    // Set up the animation
                    Animation[] animators = animateExterior ? part.FindModelAnimators(animationName) : internalProp.FindModelAnimators(animationName);
                    if (animators.Length > 0)
                    {
                        anim = animators[0];
                    }
                    else
                    {
                        JUtil.LogErrorMessage(this, "Could not find animation \"{0}\" on {2} \"{1}\"",
                            animationName, animateExterior ? part.name : internalProp.name, animateExterior ? "part" : "prop");
                        return;
                    }
                    anim[animationName].wrapMode = WrapMode.Once;

                    if (currentState ^ reverse)
                    {
                        anim[animationName].speed = float.MaxValue;
                        anim[animationName].normalizedTime = 0;

                    }
                    else
                    {
                        anim[animationName].speed = float.MinValue;
                        anim[animationName].normalizedTime = 1;
                    }
                    anim.Play(animationName);
                }
                else if (!string.IsNullOrEmpty(coloredObject))
                {
                    // Set up the color shift.
                    colorShiftRenderer = internalProp.FindModelComponent<Renderer>(coloredObject);
                    disabledColorValue = ConfigNode.ParseColor32(disabledColor);
                    enabledColorValue = ConfigNode.ParseColor32(enabledColor);
                    colorShiftRenderer.material.SetColor(colorName, (currentState ^ reverse ? enabledColorValue : disabledColorValue));
                }
                else
                {
                    JUtil.LogMessage(this, "Warning, neither color nor animation are defined.");
                }

                audioOutput = JUtil.SetupIVASound(internalProp, switchSound, switchSoundVolume, false);

                startupComplete = true;
            }
            catch
            {
                JUtil.AnnoyUser(this);
                enabled = false;
                throw;
            }
        }
Beispiel #4
0
        public void Start()
        {
            // I guess I shouldn't have expected Squad to actually do something nice for a modder like that.
            // In 0.23, loading in non-alphabetical order is still broken.

            // But now we have KSPAssembly and KSPAssemblyDependency, which actually sidestep the issue, and finally
            // Mu told someone about it and now I can avoid this path hardlinking.
            // Actually, better yet. Let it check for the new canonical location instead. Because f**k installation problems.
            if (!JSI.InstallationPathWarning.Warn())
            {
                return;
            }

            GameEvents.onUndock.Add(UndockCallback);

            if (!string.IsNullOrEmpty(itemColor))
            {
                itemColorValue = ConfigNode.ParseColor32(itemColor);
            }
            if (!string.IsNullOrEmpty(selectedColor))
            {
                selectedColorValue = ConfigNode.ParseColor32(selectedColor);
            }
            if (!string.IsNullOrEmpty(unavailableColor))
            {
                unavailableColorValue = ConfigNode.ParseColor32(unavailableColor);
            }

            topMenu.labelColor    = JUtil.ColorToColorTag(itemColorValue);
            topMenu.selectedColor = JUtil.ColorToColorTag(selectedColorValue);
            topMenu.disabledColor = JUtil.ColorToColorTag(unavailableColorValue);

            RasterPropMonitorComputer rpmComp = RasterPropMonitorComputer.Instantiate(internalProp, true);
            Func <bool> isMjAvailable         = (Func <bool>)rpmComp.GetMethod("JSIMechJeb:GetMechJebAvailable", internalProp, typeof(Func <bool>));

            if (isMjAvailable == null)
            {
                throw new NotImplementedException("isMjAvailable");
            }
            UpdateMethods(rpmComp);

            // If MechJeb is installed, but not found on the craft, menu options can't be populated correctly.
            if (isMjAvailable())
            {
                mjAvailable       = true;
                smartassAvailable = GetModuleExists("MechJebModuleSmartASS");

                topMenu.Add(new TextMenu.Item(JSIMechJeb.TargetTexts[(int)JSIMechJeb.Target.OFF], SmartASS_Off));
                topMenu.Add(new TextMenu.Item(JSIMechJeb.TargetTexts[(int)JSIMechJeb.Target.KILLROT].Replace('\n', ' '), SmartASS_KillRot));
                nodeMenuItem = new TextMenu.Item(JSIMechJeb.TargetTexts[(int)JSIMechJeb.Target.NODE], SmartASS_Node);
                topMenu.Add(nodeMenuItem);
                topMenu.Add(new TextMenu.Item(JSIMechJeb.ModeTexts[(int)JSIMechJeb.Mode.ORBITAL], OrbitalMenu));
                topMenu.Add(new TextMenu.Item(JSIMechJeb.ModeTexts[(int)JSIMechJeb.Mode.SURFACE], SurfaceMenu));
                targetMenuItem = new TextMenu.Item(JSIMechJeb.ModeTexts[(int)JSIMechJeb.Mode.TARGET], TargetMenu);
                topMenu.Add(targetMenuItem);
                forceRollMenuItem = new TextMenu.Item(String.Format("Force Roll: {0:f0}", GetForceRollAngle()), ToggleForceRoll);
                topMenu.Add(forceRollMenuItem);
                topMenu.Add(new TextMenu.Item("Execute Next Node", ExecuteNode, (int)MJMenu.ExecuteNodeMenu));
                topMenu.Add(new TextMenu.Item("Ascent Guidance", AscentGuidance, (int)MJMenu.AscentGuidanceMenu));
                topMenu.Add(new TextMenu.Item("Land Somewhere", LandingGuidance, (int)MJMenu.LandingGuidanceMenu));
                topMenu.Add(new TextMenu.Item("Docking Guidance", DockingGuidance, (int)MJMenu.DockingGuidanceMenu));
                //topMenu.Add(new TextMenu.Item("Hold Alt & Heading", SpaceplaneGuidance, (int)MJMenu.SpacePlaneMenu));
                topMenu.Add(new TextMenu.Item("Circularize", CircularizeMenu, (int)MJMenu.CircularizeMenu));
            }
            else
            {
                mjAvailable       = false;
                smartassAvailable = false;
            }
            activeMenu = topMenu;
        }
Beispiel #5
0
        private void UpdateMethods(RasterPropMonitorComputer rpmComp)
        {
            GetSmartassMode      = (Func <int>)rpmComp.GetMethod("JSIMechJeb:GetSmartassMode", internalProp, typeof(Func <int>));
            SetSmartassMode      = (Action <JSIMechJeb.Target>)rpmComp.GetMethod("JSIMechJeb:SetSmartassMode", internalProp, typeof(Action <JSIMechJeb.Target>));
            SetForceRoll         = (Action <bool, double>)rpmComp.GetMethod("JSIMechJeb:ForceRoll", internalProp, typeof(Action <bool, double>));
            GetModuleExists      = (Func <string, bool>)rpmComp.GetMethod("JSIMechJeb:GetModuleExists", internalProp, typeof(Func <string, bool>));
            CircularizeAt        = (Action <double>)rpmComp.GetMethod("JSIMechJeb:CircularizeAt", internalProp, typeof(Action <double>));
            PositionTargetExists = (Func <bool>)rpmComp.GetMethod("JSIMechJeb:PositionTargetExists", internalProp, typeof(Func <bool>));
            AutopilotEnabled     = (Func <bool>)rpmComp.GetMethod("JSIMechJeb:AutopilotEnabled", internalProp, typeof(Func <bool>));
            GetForceRollAngle    = (Func <double>)rpmComp.GetMethod("JSIMechJeb:GetForceRollAngle", internalProp, typeof(Func <double>));

            AscentAP      = (Action <bool>)rpmComp.GetMethod("JSIMechJeb:ButtonAscentGuidance", internalProp, typeof(Action <bool>));
            AscentAPState = (Func <bool>)rpmComp.GetMethod("JSIMechJeb:ButtonAscentGuidanceState", internalProp, typeof(Func <bool>));

            LandingAP      = (Action <bool>)rpmComp.GetMethod("JSIMechJeb:ButtonLandingGuidance", internalProp, typeof(Action <bool>));
            LandingAPState = (Func <bool>)rpmComp.GetMethod("JSIMechJeb:ButtonLandingGuidanceState", internalProp, typeof(Func <bool>));

            DockingAP      = (Action <bool>)rpmComp.GetMethod("JSIMechJeb:ButtonDockingGuidance", internalProp, typeof(Action <bool>));
            DockingAPState = (Func <bool>)rpmComp.GetMethod("JSIMechJeb:ButtonDockingGuidanceState", internalProp, typeof(Func <bool>));

            ForceRoll      = (Action <bool>)rpmComp.GetMethod("JSIMechJeb:ButtonForceRoll", internalProp, typeof(Action <bool>));
            ForceRollState = (Func <bool>)rpmComp.GetMethod("JSIMechJeb:ButtonForceRollState", internalProp, typeof(Func <bool>));

            ExecuteNextNode      = (Action <bool>)rpmComp.GetMethod("JSIMechJeb:ButtonNodeExecute", internalProp, typeof(Action <bool>));
            ExecuteNextNodeState = (Func <bool>)rpmComp.GetMethod("JSIMechJeb:ButtonNodeExecuteState", internalProp, typeof(Func <bool>));
        }
        private void UpdateMethods(RasterPropMonitorComputer rpmComp)
        {
            GetSmartassMode = (Func<int>)rpmComp.GetMethod("JSIMechJeb:GetSmartassMode", internalProp, typeof(Func<int>));
            SetSmartassMode = (Action<JSIMechJeb.Target>)rpmComp.GetMethod("JSIMechJeb:SetSmartassMode", internalProp, typeof(Action<JSIMechJeb.Target>));
            SetForceRoll = (Action<bool, double>)rpmComp.GetMethod("JSIMechJeb:ForceRoll", internalProp, typeof(Action<bool, double>));
            GetModuleExists = (Func<string, bool>)rpmComp.GetMethod("JSIMechJeb:GetModuleExists", internalProp, typeof(Func<string, bool>));
            CircularizeAt = (Action<double>)rpmComp.GetMethod("JSIMechJeb:CircularizeAt", internalProp, typeof(Action<double>));
            PositionTargetExists = (Func<bool>)rpmComp.GetMethod("JSIMechJeb:PositionTargetExists", internalProp, typeof(Func<bool>));
            AutopilotEnabled = (Func<bool>)rpmComp.GetMethod("JSIMechJeb:AutopilotEnabled", internalProp, typeof(Func<bool>));
            GetForceRollAngle = (Func<double>)rpmComp.GetMethod("JSIMechJeb:GetForceRollAngle", internalProp, typeof(Func<double>));

            AscentAP = (Action<bool>)rpmComp.GetMethod("JSIMechJeb:ButtonAscentGuidance", internalProp, typeof(Action<bool>));
            AscentAPState = (Func<bool>)rpmComp.GetMethod("JSIMechJeb:ButtonAscentGuidanceState", internalProp, typeof(Func<bool>));

            LandingAP = (Action<bool>)rpmComp.GetMethod("JSIMechJeb:ButtonLandingGuidance", internalProp, typeof(Action<bool>));
            LandingAPState = (Func<bool>)rpmComp.GetMethod("JSIMechJeb:ButtonLandingGuidanceState", internalProp, typeof(Func<bool>));

            DockingAP = (Action<bool>)rpmComp.GetMethod("JSIMechJeb:ButtonDockingGuidance", internalProp, typeof(Action<bool>));
            DockingAPState = (Func<bool>)rpmComp.GetMethod("JSIMechJeb:ButtonDockingGuidanceState", internalProp, typeof(Func<bool>));

            ForceRoll = (Action<bool>)rpmComp.GetMethod("JSIMechJeb:ButtonForceRoll", internalProp, typeof(Action<bool>));
            ForceRollState = (Func<bool>)rpmComp.GetMethod("JSIMechJeb:ButtonForceRollState", internalProp, typeof(Func<bool>));

            ExecuteNextNode = (Action<bool>)rpmComp.GetMethod("JSIMechJeb:ButtonNodeExecute", internalProp, typeof(Action<bool>));
            ExecuteNextNodeState = (Func<bool>)rpmComp.GetMethod("JSIMechJeb:ButtonNodeExecuteState", internalProp, typeof(Func<bool>));
        }
Beispiel #7
0
        public void Start()
        {
            if (HighLogic.LoadedSceneIsEditor)
            {
                return;
            }

            try
            {
                rpmComp = RasterPropMonitorComputer.Instantiate(internalProp, true);

                if (!groupList.ContainsKey(actionName) && !customGroupList.ContainsKey(actionName))
                {
                    JUtil.LogErrorMessage(this, "Action \"{0}\" is not supported.", actionName);
                    return;
                }

                // Parse the needs-electric-charge here.
                if (!string.IsNullOrEmpty(needsElectricCharge))
                {
                    switch (needsElectricCharge.ToLowerInvariant().Trim())
                    {
                    case "true":
                    case "yes":
                    case "1":
                        needsElectricChargeValue = true;
                        break;

                    case "false":
                    case "no":
                    case "0":
                        needsElectricChargeValue = false;
                        break;
                    }
                }

                // Now parse consumeOnToggle and consumeWhileActive...
                if (!string.IsNullOrEmpty(consumeOnToggle))
                {
                    string[] tokens = consumeOnToggle.Split(',');
                    if (tokens.Length == 3)
                    {
                        consumeOnToggleName = tokens[0].Trim();
                        if (!(PartResourceLibrary.Instance.GetDefinition(consumeOnToggleName) != null &&
                              float.TryParse(tokens[1].Trim(), NumberStyles.Any, CultureInfo.InvariantCulture,
                                             out consumeOnToggleAmount)))
                        {
                            JUtil.LogErrorMessage(this, "Could not parse \"{0}\"", consumeOnToggle);
                        }
                        switch (tokens[2].Trim().ToLower())
                        {
                        case "on":
                            consumingOnToggleUp = true;
                            break;

                        case "off":
                            consumingOnToggleDown = true;
                            break;

                        case "both":
                            consumingOnToggleUp   = true;
                            consumingOnToggleDown = true;
                            break;

                        default:
                            JUtil.LogErrorMessage(this, "So should I consume resources when turning on, turning off, or both in \"{0}\"?", consumeOnToggle);
                            break;
                        }
                    }
                }

                if (!string.IsNullOrEmpty(consumeWhileActive))
                {
                    string[] tokens = consumeWhileActive.Split(',');
                    if (tokens.Length == 2)
                    {
                        consumeWhileActiveName = tokens[0].Trim();
                        if (!(PartResourceLibrary.Instance.GetDefinition(consumeWhileActiveName) != null &&
                              float.TryParse(tokens[1].Trim(),
                                             NumberStyles.Any, CultureInfo.InvariantCulture,
                                             out consumeWhileActiveAmount)))
                        {
                            JUtil.LogErrorMessage(this, "Could not parse \"{0}\"", consumeWhileActive);
                        }
                        else
                        {
                            consumingWhileActive = true;
                            JUtil.LogMessage(this, "Switch in prop {0} prop id {1} will consume {2} while active at a rate of {3}", internalProp.propName,
                                             internalProp.propID, consumeWhileActiveName, consumeWhileActiveAmount);
                        }
                    }
                }

                if (groupList.ContainsKey(actionName))
                {
                    kspAction    = groupList[actionName];
                    currentState = vessel.ActionGroups[kspAction];
                    // action group switches may not belong to a radio group
                    switchGroupIdentifier = -1;
                }
                else
                {
                    isCustomAction = true;
                    switch (actionName)
                    {
                    case "intlight":
                        persistentVarName = internalLightName;
                        if (!string.IsNullOrEmpty(internalLightName))
                        {
                            Light[] availableLights = internalModel.FindModelComponents <Light>();
                            if (availableLights != null && availableLights.Length > 0)
                            {
                                List <Light> lights = new List <Light>(availableLights);
                                for (int i = lights.Count - 1; i >= 0; --i)
                                {
                                    if (lights[i].name != internalLightName)
                                    {
                                        lights.RemoveAt(i);
                                    }
                                }
                                if (lights.Count > 0)
                                {
                                    lightObjects              = lights.ToArray();
                                    needsElectricChargeValue |= string.IsNullOrEmpty(needsElectricCharge) || needsElectricChargeValue;
                                }
                                else
                                {
                                    actionName = "dummy";
                                }
                            }
                        }
                        else
                        {
                            actionName = "dummy";
                        }
                        break;

                    case "plugin":
                        persistentVarName = string.Empty;
                        rpmComp.UpdateDataRefreshRate(refreshRate);

                        foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes("PROP"))
                        {
                            if (node.GetValue("name") == internalProp.propName)
                            {
                                foreach (ConfigNode pluginConfig in node.GetNodes("MODULE")[moduleID].GetNodes("PLUGINACTION"))
                                {
                                    if (pluginConfig.HasValue("name") && pluginConfig.HasValue("actionMethod"))
                                    {
                                        string action = pluginConfig.GetValue("name").Trim() + ":" + pluginConfig.GetValue("actionMethod").Trim();
                                        actionHandler = (Action <bool>)rpmComp.GetMethod(action, internalProp, typeof(Action <bool>));

                                        if (actionHandler == null)
                                        {
                                            JUtil.LogErrorMessage(this, "Failed to instantiate action handler {0}", action);
                                        }
                                        else
                                        {
                                            if (pluginConfig.HasValue("stateMethod"))
                                            {
                                                string state = pluginConfig.GetValue("name").Trim() + ":" + pluginConfig.GetValue("stateMethod").Trim();
                                                stateVariable = rpmComp.InstantiateVariableOrNumber("PLUGIN_" + state);
                                            }
                                            else if (pluginConfig.HasValue("stateVariable"))
                                            {
                                                stateVariable = rpmComp.InstantiateVariableOrNumber(pluginConfig.GetValue("stateVariable").Trim());
                                            }
                                            isPluginAction = true;
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                        if (actionHandler == null)
                        {
                            actionName = "dummy";
                            JUtil.LogMessage(this, "Plugin handlers did not start, reverting to dummy mode.");
                        }
                        break;

                    case "transfer":
                        persistentVarName = string.Empty;
                        rpmComp.UpdateDataRefreshRate(refreshRate);

                        foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes("PROP"))
                        {
                            if (node.GetValue("name") == internalProp.propName)
                            {
                                foreach (ConfigNode pluginConfig in node.GetNodes("MODULE")[moduleID].GetNodes("TRANSFERACTION"))
                                {
                                    if (pluginConfig.HasValue("name") || pluginConfig.HasValue("getVariable"))
                                    {
                                        if (pluginConfig.HasValue("stateMethod"))
                                        {
                                            string state = pluginConfig.GetValue("name").Trim() + ":" + pluginConfig.GetValue("stateMethod").Trim();
                                            stateVariable = rpmComp.InstantiateVariableOrNumber("PLUGIN_" + state);
                                        }
                                        else if (pluginConfig.HasValue("stateVariable"))
                                        {
                                            stateVariable = rpmComp.InstantiateVariableOrNumber(pluginConfig.GetValue("stateVariable").Trim());
                                        }
                                        if (pluginConfig.HasValue("setMethod"))
                                        {
                                            string action = pluginConfig.GetValue("name").Trim() + ":" + pluginConfig.GetValue("setMethod").Trim();
                                            transferSetter = (Action <double>)rpmComp.GetMethod(action, internalProp, typeof(Action <double>));

                                            if (transferSetter == null)
                                            {
                                                JUtil.LogErrorMessage(this, "Failed to instantiate transfer handler {0}", pluginConfig.GetValue("name"));
                                            }
                                            else if (pluginConfig.HasValue("perPodPersistenceName"))
                                            {
                                                transferPersistentName = pluginConfig.GetValue("perPodPersistenceName").Trim();
                                                actionName             = "transferFromPersistent";
                                                customAction           = CustomActions.TransferFromPersistent;
                                            }
                                            else if (pluginConfig.HasValue("getVariable"))
                                            {
                                                transferGetter = rpmComp.InstantiateVariableOrNumber(pluginConfig.GetValue("getVariable").Trim());
                                                actionName     = "transferFromVariable";
                                                customAction   = CustomActions.TransferFromVariable;
                                            }
                                            else
                                            {
                                                JUtil.LogErrorMessage(this, "Unable to configure transfer setter method in {0} - no perPodPersistenceName or getVariable", internalProp.name);
                                                transferSetter = null;
                                                //JUtil.LogMessage(this, "Got setter {0}", action);
                                            }
                                        }
                                        else if (pluginConfig.HasValue("getMethod"))
                                        {
                                            if (pluginConfig.HasValue("perPodPersistenceName"))
                                            {
                                                string action = pluginConfig.GetValue("name").Trim() + ":" + pluginConfig.GetValue("getMethod").Trim();
                                                var    getter = (Func <double>)rpmComp.GetMethod(action, internalProp, typeof(Func <double>));

                                                if (getter == null)
                                                {
                                                    JUtil.LogErrorMessage(this, "Failed to instantiate transfer handler {0} in {1}", pluginConfig.GetValue("name"), internalProp.name);
                                                }
                                                else
                                                {
                                                    transferGetter         = rpmComp.InstantiateVariableOrNumber("PLUGIN_" + action);
                                                    transferPersistentName = pluginConfig.GetValue("perPodPersistenceName").Trim();
                                                    actionName             = "transferToPersistent";
                                                    customAction           = CustomActions.TransferToPersistent;
                                                    //JUtil.LogMessage(this, "Got getter {0}", action);
                                                    break;
                                                }
                                            }
                                            else
                                            {
                                                JUtil.LogErrorMessage(this, "Transfer handler in {0} configured with 'getVariable', but no 'perPodPeristenceName'", internalProp.name);
                                            }
                                        }
                                        else if (pluginConfig.HasValue("getVariable"))
                                        {
                                            if (pluginConfig.HasValue("perPodPersistenceName"))
                                            {
                                                transferGetter         = rpmComp.InstantiateVariableOrNumber(pluginConfig.GetValue("getVariable").Trim());
                                                transferPersistentName = pluginConfig.GetValue("perPodPersistenceName").Trim();
                                                actionName             = "transferToPersistent";
                                                customAction           = CustomActions.TransferToPersistent;
                                            }
                                            else
                                            {
                                                JUtil.LogErrorMessage(this, "Transfer handler in {0} configured with 'getVariable', but no 'perPodPeristenceName'", internalProp.name);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if (transferGetter == null && transferSetter == null)
                        {
                            actionName    = "dummy";
                            stateVariable = null;
                            JUtil.LogMessage(this, "Transfer handlers did not start, reverting to dummy mode.");
                        }
                        break;

                    default:
                        persistentVarName = "switch" + internalProp.propID + "_" + moduleID;
                        break;
                    }
                    if (!string.IsNullOrEmpty(perPodPersistenceName))
                    {
                        persistentVarName = perPodPersistenceName;
                    }
                    else
                    {
                        // If there's no persistence name, there's no valid group id for this switch
                        switchGroupIdentifier = -1;
                    }

                    persistentVarValid = !string.IsNullOrEmpty(persistentVarName);
                }

                perPodPersistenceValid = !string.IsNullOrEmpty(perPodPersistenceName);

                if (customGroupList.ContainsKey(actionName))
                {
                    customAction = customGroupList[actionName];
                }

                if (needsElectricChargeValue || persistentVarValid || !string.IsNullOrEmpty(perPodMasterSwitchName) || !string.IsNullOrEmpty(masterVariableName) ||
                    transferGetter != null || transferSetter != null)
                {
                    rpmComp.UpdateDataRefreshRate(refreshRate);

                    if (!string.IsNullOrEmpty(masterVariableName))
                    {
                        string[] range = masterVariableRange.Split(',');
                        if (range.Length == 2)
                        {
                            masterVariable = new VariableOrNumberRange(rpmComp, masterVariableName, range[0], range[1]);
                        }
                        else
                        {
                            masterVariable = null;
                        }
                    }
                }

                if (needsElectricChargeValue)
                {
                    del = (Action <bool>)Delegate.CreateDelegate(typeof(Action <bool>), this, "ResourceDepletedCallback");
                    rpmComp.RegisterResourceCallback(resourceName, del);
                }

                // set up the toggle switch
                if (!string.IsNullOrEmpty(switchTransform))
                {
                    if (momentarySwitch)
                    {
                        SmarterButton.CreateButton(internalProp, switchTransform, Click, Click);
                    }
                    else
                    {
                        SmarterButton.CreateButton(internalProp, switchTransform, Click);
                    }
                }

                if (isCustomAction)
                {
                    if (isPluginAction && stateVariable != null)
                    {
                        currentState = stateVariable.AsInt() > 0;
                    }
                    else
                    {
                        if (persistentVarValid)
                        {
                            if (switchGroupIdentifier >= 0)
                            {
                                int activeSwitch = rpmComp.GetPersistentVariable(persistentVarName, 0, perPodPersistenceIsGlobal).MassageToInt();

                                currentState = customGroupState = (switchGroupIdentifier == activeSwitch);
                            }
                            else
                            {
                                currentState = customGroupState = rpmComp.GetPersistentVariable(persistentVarName, initialState, perPodPersistenceIsGlobal);
                            }

                            if (customAction == CustomActions.IntLight)
                            {
                                // We have to restore lighting after reading the
                                // persistent variable.
                                SetInternalLights(customGroupState);
                            }
                        }
                    }
                }

                if (persistentVarValid && !rpmComp.HasPersistentVariable(persistentVarName, perPodPersistenceIsGlobal))
                {
                    if (switchGroupIdentifier >= 0)
                    {
                        if (currentState)
                        {
                            rpmComp.SetPersistentVariable(persistentVarName, switchGroupIdentifier, perPodPersistenceIsGlobal);
                        }
                    }
                    else
                    {
                        rpmComp.SetPersistentVariable(persistentVarName, currentState, perPodPersistenceIsGlobal);
                    }
                }

                if (!string.IsNullOrEmpty(animationName))
                {
                    // Set up the animation
                    Animation[] animators = animateExterior ? part.FindModelAnimators(animationName) : internalProp.FindModelAnimators(animationName);
                    if (animators.Length > 0)
                    {
                        anim = animators[0];
                    }
                    else
                    {
                        JUtil.LogErrorMessage(this, "Could not find animation \"{0}\" on {2} \"{1}\"",
                                              animationName, animateExterior ? part.name : internalProp.name, animateExterior ? "part" : "prop");
                        return;
                    }
                    anim[animationName].wrapMode = WrapMode.Once;

                    if (currentState ^ reverse)
                    {
                        anim[animationName].speed          = float.MaxValue;
                        anim[animationName].normalizedTime = 0;
                    }
                    else
                    {
                        anim[animationName].speed          = float.MinValue;
                        anim[animationName].normalizedTime = 1;
                    }
                    anim.Play(animationName);
                }
                else if (!string.IsNullOrEmpty(coloredObject))
                {
                    // Set up the color shift.
                    Renderer colorShiftRenderer = internalProp.FindModelComponent <Renderer>(coloredObject);
                    disabledColorValue = JUtil.ParseColor32(disabledColor, part, ref rpmComp);
                    enabledColorValue  = JUtil.ParseColor32(enabledColor, part, ref rpmComp);
                    colorShiftMaterial = colorShiftRenderer.material;
                    colorNameId        = Shader.PropertyToID(colorName);
                    colorShiftMaterial.SetColor(colorNameId, (currentState ^ reverse ? enabledColorValue : disabledColorValue));
                }
                else
                {
                    JUtil.LogMessage(this, "Warning, neither color nor animation are defined in prop {0} #{1} (this may be okay).", internalProp.propName, internalProp.propID);
                }

                audioOutput = JUtil.SetupIVASound(internalProp, switchSound, switchSoundVolume, false);

                if (!string.IsNullOrEmpty(loopingSound) && loopingSoundVolume > 0.0f)
                {
                    loopingOutput = JUtil.SetupIVASound(internalProp, loopingSound, loopingSoundVolume, true);
                }

                perPodMasterSwitchValid = !string.IsNullOrEmpty(perPodMasterSwitchName);

                JUtil.LogMessage(this, "Configuration complete in prop {0} ({1}).", internalProp.propID, internalProp.propName);

                startupComplete = true;
            }
            catch (Exception e)
            {
                JUtil.LogErrorMessage(this, "Exception configuring prop {0} ({1}): {2}", internalProp.propID, internalProp.propName, e);
                JUtil.AnnoyUser(this);
                enabled = false;
            }
        }