/// <summary> /// Do a "broadcast" GetPersistentVariable, where we iterate over every /// part in the craft and find each RasterPropMonitorComputer. Return /// the value from the first one that contains the variable, or the /// default value otherwise. /// </summary> /// <param name="name"></param> /// <param name="defaultValue"></param> /// <returns></returns> internal object GetPersistentVariable(string name, object defaultValue) { if (vessel != null) { for (int partIdx = 0; partIdx < vessel.parts.Count; ++partIdx) { RasterPropMonitorComputer rpmc = RasterPropMonitorComputer.Instantiate(vessel.parts[partIdx], false); if (rpmc.HasPersistentVariable(name, false)) { return(rpmc.GetPersistentVariable(name, defaultValue, false)); } } } return(defaultValue); }
public void Start() { // If we're not in the correct location, there's no point doing anything. if (!InstallationPathWarning.Warn()) { return; } if (HighLogic.LoadedSceneIsEditor) { return; } try { rpmComp = RasterPropMonitorComputer.Instantiate(internalProp, true); JUtil.LogMessage(this, "Attaching monitor {2}-{1} to {0}", rpmComp.RPMCid, internalProp.propID, internalProp.internalModel.internalName); // Install the calculator module. rpmComp.UpdateDataRefreshRate(refreshDataRate); // Loading the font... List <Texture2D> fontTexture = new List <Texture2D>(); fontTexture.Add(LoadFont(this, internalProp, fontTransform)); // Damn KSP's config parser!!! if (!string.IsNullOrEmpty(emptyColor)) { emptyColorValue = ConfigNode.ParseColor32(emptyColor); } if (!string.IsNullOrEmpty(defaultFontTint)) { defaultFontTintValue = ConfigNode.ParseColor32(defaultFontTint); } if (!string.IsNullOrEmpty(fontDefinition)) { JUtil.LogMessage(this, "Loading font definition from {0}", fontDefinition); fontDefinitionString = File.ReadAllLines(KSPUtil.ApplicationRootPath + "GameData/" + fontDefinition.EnforceSlashes(), Encoding.UTF8)[0]; } // Now that is done, proceed to setting up the screen. screenTexture = new RenderTexture(screenPixelWidth, screenPixelHeight, 24, RenderTextureFormat.ARGB32); screenMat = internalProp.FindModelTransform(screenTransform).GetComponent <Renderer>().material; bool manuallyInvertY = false; if (SystemInfo.graphicsDeviceVersion.StartsWith("Direct3D 9") || SystemInfo.graphicsDeviceVersion.StartsWith("Direct3D 11") || SystemInfo.graphicsDeviceVersion.StartsWith("Direct3D 12")) { manuallyInvertY = (UnityEngine.QualitySettings.antiAliasing > 0); } foreach (string layerID in textureLayerID.Split()) { screenMat.SetTexture(layerID.Trim(), screenTexture); // This code was written for a much older flavor of Unity, and the Unity 2017.1 update broke // some assumptions about who managed the y-inversion issue between OpenGL and DX9. if (manuallyInvertY) { screenMat.SetTextureScale(layerID.Trim(), new Vector2(1.0f, -1.0f)); screenMat.SetTextureOffset(layerID.Trim(), new Vector2(0.0f, 1.0f)); } } if (GameDatabase.Instance.ExistsTexture(noSignalTextureURL.EnforceSlashes())) { noSignalTexture = GameDatabase.Instance.GetTexture(noSignalTextureURL.EnforceSlashes(), false); } // The neat trick. IConfigNode doesn't work. No amount of kicking got it to work. // Well, we don't need it. GameDatabase, gimme config nodes for all props! foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes("PROP")) { // Now, we know our own prop name. if (node.GetValue("name") == internalProp.propName) { // So this is the configuration of our prop in memory. Nice place. // We know it contains at least one MODULE node, us. // And we know our moduleID, which is the number in order of being listed in the prop. // Therefore the module by that number is our module's own config node. ConfigNode moduleConfig = node.GetNodes("MODULE")[moduleID]; ConfigNode[] pageNodes = moduleConfig.GetNodes("PAGE"); // Which we can now parse for page definitions. for (int i = 0; i < pageNodes.Length; i++) { // Mwahahaha. try { var newPage = new MonitorPage(i, pageNodes[i], this); activePage = activePage ?? newPage; if (newPage.isDefault) { activePage = newPage; } pages.Add(newPage); } catch (ArgumentException e) { JUtil.LogMessage(this, "Warning - {0}", e); } } // Now that all pages are loaded, we can use the moment in the loop to suck in all the extra fonts. foreach (string value in moduleConfig.GetValues("extraFont")) { fontTexture.Add(LoadFont(this, internalProp, value)); } break; } } JUtil.LogMessage(this, "Done setting up pages, {0} pages ready.", pages.Count); textRenderer = new TextRenderer(fontTexture, new Vector2((float)fontLetterWidth, (float)fontLetterHeight), fontDefinitionString, 17, screenPixelWidth, screenPixelHeight); // Load our state from storage... persistentVarName = "activePage" + internalProp.propID; int activePageID = rpmComp.GetPersistentVariable(persistentVarName, pages.Count, false).MassageToInt(); if (activePageID < pages.Count) { activePage = pages[activePageID]; } activePage.Active(true); // If we have global buttons, set them up. if (!string.IsNullOrEmpty(globalButtons)) { string[] tokens = globalButtons.Split(','); for (int i = 0; i < tokens.Length; i++) { string buttonName = tokens[i].Trim(); // Notice that holes in the global button list ARE legal. if (!string.IsNullOrEmpty(buttonName)) { SmarterButton.CreateButton(internalProp, buttonName, i, GlobalButtonClick, GlobalButtonRelease); } } } audioOutput = JUtil.SetupIVASound(internalProp, buttonClickSound, buttonClickVolume, false); if (needsElectricCharge) { delResourceCallback = (Action <bool>)Delegate.CreateDelegate(typeof(Action <bool>), this, "ResourceDepletedCallback"); rpmComp.RegisterResourceCallback(resourceName, delResourceCallback); } if (needsCommConnection) { delCommConnectionCallback = (Action <float>)Delegate.CreateDelegate(typeof(Action <float>), this, "CommConnectionCallback"); rpmComp.RegisterVariableCallback("COMMNETVESSELCONTROLSTATE", delCommConnectionCallback); } // And if the try block never completed, startupComplete will never be true. startupComplete = true; } catch { JUtil.AnnoyUser(this); // We can also disable ourselves, that should help. enabled = false; // And now that we notified the user that config is borked, we rethrow the exception so that // it gets logged and we can debug. throw; } }
public void Start() { if (!HighLogic.LoadedSceneIsFlight) return; rpmComp = RasterPropMonitorComputer.Instantiate(internalProp, true); // Grrrrrr. if (!string.IsNullOrEmpty(nameColor)) nameColorValue = ConfigNode.ParseColor32(nameColor); if (!string.IsNullOrEmpty(distanceColor)) distanceColorValue = ConfigNode.ParseColor32(distanceColor); if (!string.IsNullOrEmpty(selectedColor)) selectedColorValue = ConfigNode.ParseColor32(selectedColor); if (!string.IsNullOrEmpty(unavailableColor)) unavailableColorValue = ConfigNode.ParseColor32(unavailableColor); persistentVarName = "targetfilter" + internalProp.propID; // 7 is the bitmask for ship-station-probe; VesselFilterFromBitmask(rpmComp.GetPersistentVariable(persistentVarName, defaultFilter, false).MassageToInt()); nameColorTag = JUtil.ColorToColorTag(nameColorValue); distanceColorTag = JUtil.ColorToColorTag(distanceColorValue); selectedColorTag = JUtil.ColorToColorTag(selectedColorValue); unavailableColorTag = JUtil.ColorToColorTag(unavailableColorValue); distanceFormatString = distanceFormatString.UnMangleConfigText(); menuTitleFormatString = menuTitleFormatString.UnMangleConfigText(); topMenu.labelColor = nameColorTag; topMenu.selectedColor = selectedColorTag; topMenu.disabledColor = unavailableColorTag; if (!string.IsNullOrEmpty(pageTitle)) pageTitle = pageTitle.UnMangleConfigText(); foreach (CelestialBody body in FlightGlobals.Bodies) { celestialsList.Add(new Celestial(body, vessel.transform.position)); } FindReferencePoints(); UpdateUndockablesList(); var menuActions = new List<Action<int, TextMenu.Item>>(); menuActions.Add(ShowCelestialMenu); menuActions.Add(ShowVesselMenu); menuActions.Add(ShowSpaceObjectMenu); menuActions.Add(ShowReferenceMenu); menuActions.Add(ShowUndockMenu); menuActions.Add(ArmGrapple); menuActions.Add(ShowFiltersMenu); menuActions.Add(ClearTarget); menuActions.Add(ShowCrewEVA); for (int i = 0; i < rootMenu.Count; ++i) { var menuitem = new TextMenu.Item(); menuitem.labelText = rootMenu[i]; menuitem.action = menuActions[i]; topMenu.Add(menuitem); switch (menuitem.labelText) { case clearTargetItemText: clearTarget = topMenu[i]; break; case undockItemText: undockMenuItem = topMenu[i]; break; case armGrappleText: grappleMenuItem = topMenu[i]; break; case crewEvaText: float acLevel = ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.AstronautComplex); bool evaUnlocked = GameVariables.Instance.UnlockedEVA(acLevel); menuitem.isDisabled = !GameVariables.Instance.EVAIsPossible(evaUnlocked, vessel); break; } } activeMenu = topMenu; }
public void Start() { // If we're not in the correct location, there's no point doing anything. if (!InstallationPathWarning.Warn()) { return; } if (HighLogic.LoadedSceneIsEditor) { return; } try { rpmComp = RasterPropMonitorComputer.Instantiate(internalProp, true); JUtil.LogMessage(this, "Attaching monitor {2}-{1} to {0}", rpmComp.RPMCid, internalProp.propID, internalProp.internalModel.internalName); // Install the calculator module. rpmComp.UpdateDataRefreshRate(refreshDataRate); // Loading the font... List<Texture2D> fontTexture = new List<Texture2D>(); fontTexture.Add(LoadFont(this, internalProp, fontTransform)); // Damn KSP's config parser!!! if (!string.IsNullOrEmpty(emptyColor)) { emptyColorValue = ConfigNode.ParseColor32(emptyColor); } if (!string.IsNullOrEmpty(defaultFontTint)) { defaultFontTintValue = ConfigNode.ParseColor32(defaultFontTint); } if (!string.IsNullOrEmpty(fontDefinition)) { JUtil.LogMessage(this, "Loading font definition from {0}", fontDefinition); fontDefinitionString = File.ReadAllLines(KSPUtil.ApplicationRootPath + "GameData/" + fontDefinition.EnforceSlashes(), Encoding.UTF8)[0]; } // Now that is done, proceed to setting up the screen. screenTexture = new RenderTexture(screenPixelWidth, screenPixelHeight, 24, RenderTextureFormat.ARGB32); screenMat = internalProp.FindModelTransform(screenTransform).GetComponent<Renderer>().material; foreach (string layerID in textureLayerID.Split()) { screenMat.SetTexture(layerID.Trim(), screenTexture); } if (GameDatabase.Instance.ExistsTexture(noSignalTextureURL.EnforceSlashes())) { noSignalTexture = GameDatabase.Instance.GetTexture(noSignalTextureURL.EnforceSlashes(), false); } // Create camera instance... cameraStructure = new FlyingCamera(part, cameraAspect); // The neat trick. IConfigNode doesn't work. No amount of kicking got it to work. // Well, we don't need it. GameDatabase, gimme config nodes for all props! foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes("PROP")) { // Now, we know our own prop name. if (node.GetValue("name") == internalProp.propName) { // So this is the configuration of our prop in memory. Nice place. // We know it contains at least one MODULE node, us. // And we know our moduleID, which is the number in order of being listed in the prop. // Therefore the module by that number is our module's own config node. ConfigNode moduleConfig = node.GetNodes("MODULE")[moduleID]; ConfigNode[] pageNodes = moduleConfig.GetNodes("PAGE"); // Which we can now parse for page definitions. for (int i = 0; i < pageNodes.Length; i++) { // Mwahahaha. try { var newPage = new MonitorPage(i, pageNodes[i], this); activePage = activePage ?? newPage; if (newPage.isDefault) activePage = newPage; pages.Add(newPage); } catch (ArgumentException e) { JUtil.LogMessage(this, "Warning - {0}", e); } } // Now that all pages are loaded, we can use the moment in the loop to suck in all the extra fonts. foreach (string value in moduleConfig.GetValues("extraFont")) { fontTexture.Add(LoadFont(this, internalProp, value)); } break; } } JUtil.LogMessage(this, "Done setting up pages, {0} pages ready.", pages.Count); textRenderer = new TextRenderer(fontTexture, new Vector2((float)fontLetterWidth, (float)fontLetterHeight), fontDefinitionString, 17, screenPixelWidth, screenPixelHeight); // Load our state from storage... persistentVarName = "activePage" + internalProp.propID; int activePageID = rpmComp.GetPersistentVariable(persistentVarName, pages.Count, false).MassageToInt(); if (activePageID < pages.Count) { activePage = pages[activePageID]; } activePage.Active(true); // If we have global buttons, set them up. if (!string.IsNullOrEmpty(globalButtons)) { string[] tokens = globalButtons.Split(','); for (int i = 0; i < tokens.Length; i++) { string buttonName = tokens[i].Trim(); // Notice that holes in the global button list ARE legal. if (!string.IsNullOrEmpty(buttonName)) SmarterButton.CreateButton(internalProp, buttonName, i, GlobalButtonClick, GlobalButtonRelease); } } audioOutput = JUtil.SetupIVASound(internalProp, buttonClickSound, buttonClickVolume, false); if (needsElectricCharge) { del = (Action<bool>)Delegate.CreateDelegate(typeof(Action<bool>), this, "ResourceDepletedCallback"); rpmComp.RegisterResourceCallback(resourceName, del); } // And if the try block never completed, startupComplete will never be true. startupComplete = true; } catch { JUtil.AnnoyUser(this); // We can also disable ourselves, that should help. enabled = false; // And now that we notified the user that config is borked, we rethrow the exception so that // it gets logged and we can debug. throw; } }
public void Start() { if (!HighLogic.LoadedSceneIsFlight) { return; } rpmComp = RasterPropMonitorComputer.Instantiate(internalProp, true); // Grrrrrr. if (!string.IsNullOrEmpty(nameColor)) { nameColorValue = ConfigNode.ParseColor32(nameColor); } if (!string.IsNullOrEmpty(distanceColor)) { distanceColorValue = ConfigNode.ParseColor32(distanceColor); } if (!string.IsNullOrEmpty(selectedColor)) { selectedColorValue = ConfigNode.ParseColor32(selectedColor); } if (!string.IsNullOrEmpty(unavailableColor)) { unavailableColorValue = ConfigNode.ParseColor32(unavailableColor); } persistentVarName = "targetfilter" + internalProp.propID; // 7 is the bitmask for ship-station-probe; VesselFilterFromBitmask(rpmComp.GetPersistentVariable(persistentVarName, defaultFilter, false).MassageToInt()); nameColorTag = JUtil.ColorToColorTag(nameColorValue); distanceColorTag = JUtil.ColorToColorTag(distanceColorValue); selectedColorTag = JUtil.ColorToColorTag(selectedColorValue); unavailableColorTag = JUtil.ColorToColorTag(unavailableColorValue); distanceFormatString = distanceFormatString.UnMangleConfigText(); menuTitleFormatString = menuTitleFormatString.UnMangleConfigText(); topMenu.labelColor = nameColorTag; topMenu.selectedColor = selectedColorTag; topMenu.disabledColor = unavailableColorTag; if (!string.IsNullOrEmpty(pageTitle)) { pageTitle = pageTitle.UnMangleConfigText(); } foreach (CelestialBody body in FlightGlobals.Bodies) { celestialsList.Add(new Celestial(body, vessel.transform.position)); } FindReferencePoints(); UpdateUndockablesList(); var menuActions = new List <Action <int, TextMenu.Item> >(); menuActions.Add(ShowCelestialMenu); menuActions.Add(ShowVesselMenu); menuActions.Add(ShowSpaceObjectMenu); menuActions.Add(ShowReferenceMenu); menuActions.Add(ShowUndockMenu); menuActions.Add(ArmGrapple); menuActions.Add(ShowFiltersMenu); menuActions.Add(ClearTarget); menuActions.Add(ShowCrewEVA); for (int i = 0; i < rootMenu.Count; ++i) { var menuitem = new TextMenu.Item(); menuitem.labelText = rootMenu[i]; menuitem.action = menuActions[i]; topMenu.Add(menuitem); switch (menuitem.labelText) { case clearTargetItemText: clearTarget = topMenu[i]; break; case undockItemText: undockMenuItem = topMenu[i]; break; case armGrappleText: grappleMenuItem = topMenu[i]; break; case crewEvaText: float acLevel = ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.AstronautComplex); bool evaUnlocked = GameVariables.Instance.UnlockedEVA(acLevel); menuitem.isDisabled = !GameVariables.Instance.EVAIsPossible(evaUnlocked, vessel); break; } } activeMenu = topMenu; }
private void UpdateOdometer() { double thisUpdate = Planetarium.GetUniversalTime(); float dT = (float)(thisUpdate - lastUpdate) * odometerRotationScalar; float value; if (!string.IsNullOrEmpty(perPodPersistenceName)) { bool state = rpmComp.GetPersistentVariable(perPodPersistenceName, false, false); RPMVesselComputer comp = RPMVesselComputer.Instance(rpmComp.vessel); value = rpmComp.ProcessVariable((state) ? altVariable : variable, comp).MassageToFloat(); } else { RPMVesselComputer comp = RPMVesselComputer.Instance(rpmComp.vessel); value = rpmComp.ProcessVariable(variable, comp).MassageToFloat(); } // Make sure the value isn't going to be a problem. if (float.IsNaN(value)) { value = 0.0f; } if (value < 0.0f) { signGoalCoord = 0.625f; } else if (value > 0.0f) { signGoalCoord = 0.875f; } else { signGoalCoord = 0.75f; } signCurrentCoord = JUtil.DualLerp(signCurrentCoord, signGoalCoord, 0.0f, 1.0f, dT); value = Mathf.Abs(value); if (oMode == OdometerMode.SI) { float leadingDigitExponent; if (value < 0.001f) { leadingDigitExponent = -3.0f; } else { leadingDigitExponent = Mathf.Floor(Mathf.Log10(value)); } // siExponent is the location relative to the original decimal of // the SI prefix. Is is always the greatest multiple-of-3 less // than the leadingDigitExponent. int siIndex = (int)Mathf.Floor(leadingDigitExponent / 3.0f); if (siIndex > 3) { siIndex = 3; } int siExponent = siIndex * 3; prefixGoalCoord = (float)(siIndex + 1) * 0.125f; prefixCurrentCoord = JUtil.DualLerp(prefixCurrentCoord, prefixGoalCoord, 0.0f, 1.0f, dT); float scaledValue = value / Mathf.Pow(10.0f, (float)(siExponent - 3)); int intValue = (int)(scaledValue); for (int i = 5; i >= 0; --i) { float thisCoord = (float)(intValue % 10) / 10.0f; if (i == 5) { // So we can display fractional values: // However, we also quantize it to make it easier to // read during the transition from 9 to 0. thisCoord = Mathf.Floor((scaledValue % 10.0f) * 2.0f) / 20.0f; } intValue = intValue / 10; goalCoordinate[i] = thisCoord; } } else if (oMode == OdometerMode.TIME_HHHMMSS) { // Clamp the value value = Mathf.Min(value, 59.0f + 59.0f * 60.0f + 999.0f * 60.0f * 24.0f); // seconds float thisCoord = Mathf.Floor((value % 10.0f) * 2.0f) / 20.0f; goalCoordinate[6] = thisCoord; int intValue = (int)(value) / 10; // tens of seconds thisCoord = (float)(intValue % 6) / 10.0f; goalCoordinate[5] = thisCoord; intValue /= 6; // minutes thisCoord = (float)(intValue % 10) / 10.0f; goalCoordinate[4] = thisCoord; intValue /= 10; // tens of minutes thisCoord = (float)(intValue % 6) / 10.0f; goalCoordinate[3] = thisCoord; intValue /= 6; for (int i = 2; i >= 0; --i) { thisCoord = (float)(intValue % 10) / 10.0f; intValue = intValue / 10; goalCoordinate[i] = thisCoord; } } else { int intValue = (int)(value); for (int i = 7; i >= 0; --i) { float thisCoord = (float)(intValue % 10) / 10.0f; if (i == 7) { thisCoord = Mathf.Floor((value % 10.0f) * 2.0f) / 20.0f; } intValue = intValue / 10; goalCoordinate[i] = thisCoord; } } // Update interpolants for (int i = 0; i < 8; ++i) { if (currentCoordinate[i] != goalCoordinate[i]) { float startingPoint; float endingPoint; if (Mathf.Abs(currentCoordinate[i] - goalCoordinate[i]) <= 0.5f) { startingPoint = currentCoordinate[i]; endingPoint = goalCoordinate[i]; } else if (goalCoordinate[i] < currentCoordinate[i]) { startingPoint = currentCoordinate[i]; endingPoint = goalCoordinate[i] + 1.0f; } else { startingPoint = currentCoordinate[i] + 1.0f; endingPoint = goalCoordinate[i]; } // This lerp causes a rotation that starts quickly but // slows down close to the goal. It actually looks // pretty good for typical incrementing counts, while the // rapid spinning of small values is chaotic enough that // you can't really tell what's going on, anyway. float goal = JUtil.DualLerp(startingPoint, endingPoint, 0.0f, 1.0f, dT); if (goal > 1.0f) { goal -= 1.0f; } currentCoordinate[i] = goal; } } lastUpdate = thisUpdate; }
public void Click() { bool switchEnabled = true; if (!forcedShutdown) { if (perPodMasterSwitchValid) { switchEnabled = rpmComp.GetPersistentVariable(perPodMasterSwitchName, false, false); } if (masterVariable != null) { switchEnabled = masterVariable.IsInRange(); } } if (!switchEnabled) { // If the master switch is 'off' and we're not here because // of a forced shutdown, don't allow this switch to work. // early return return; } if (isCustomAction) { if (switchGroupIdentifier >= 0) { if (!forcedShutdown && !customGroupState) { customGroupState = true; if (persistentVarValid) { rpmComp.SetPersistentVariable(persistentVarName, switchGroupIdentifier, perPodPersistenceIsGlobal); } } // else: can't turn off a radio group switch. } else if (customAction == CustomActions.Plugin && stateVariable != null) { int ivalue = stateVariable.AsInt(); customGroupState = (ivalue < 1) && !forcedShutdown; } else { customGroupState = !customGroupState; if (persistentVarValid) { rpmComp.SetPersistentVariable(persistentVarName, customGroupState, perPodPersistenceIsGlobal); } } } else { vessel.ActionGroups.ToggleGroup(kspAction); } // Now we do extra things that with regular actions can't happen. switch (customAction) { case CustomActions.IntLight: SetInternalLights(customGroupState); break; case CustomActions.Plugin: actionHandler(customGroupState); break; case CustomActions.Stage: if (InputLockManager.IsUnlocked(ControlTypes.STAGING)) { StageManager.ActivateNextStage(); } break; case CustomActions.TransferToPersistent: if (stateVariable != null) { // stateVariable can disable the button functionality. int ivalue = stateVariable.AsInt(); if (ivalue < 1) { return; // early - button disabled } } float getValue = transferGetter.AsFloat(); rpmComp.SetPersistentVariable(transferPersistentName, getValue, false); break; case CustomActions.TransferFromPersistent: if (stateVariable != null) { // stateVariable can disable the button functionality. int ivalue = stateVariable.AsInt(); if (ivalue < 1) { return; // early - button disabled } } if (rpmComp.HasPersistentVariable(transferPersistentName, false)) { transferSetter(rpmComp.GetPersistentVariable(transferPersistentName, 0.0, false).MassageToDouble()); } break; case CustomActions.TransferFromVariable: if (stateVariable != null) { // stateVariable can disable the button functionality. int ivalue = stateVariable.AsInt(); if (ivalue < 1) { return; // early - button disabled } } double xferValue = transferGetter.AsDouble(); transferSetter(xferValue); break; } }
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; } }