/// <summary> /// Here when the add-on loads upon flight start. /// </summary> public void Start() { Logging.Log("Starting"); userSelection = AltimeterDisplayState.DEFAULT; userClickedTumbler = false; parachutes.Clear(); vesselId = uint.MaxValue; vesselPartCount = -1; isGamePaused = false; }
/// <summary> /// Here whenever the altimeter display mode gets toggled (either by the player or by this mod). /// </summary> /// <param name="data"></param> private void OnAltimeterDisplayModeToggle(AltimeterDisplayState mode) { Logging.Log("Altimeter mode changed to: " + mode); earliestAllowableToggleTime = DateTime.Now + MINIMUM_DWELL_TIME; if (userClickedTumbler) { userClickedTumbler = false; userSelection = mode; } }
/// <summary> /// Based on the current ship situation, determine what state the altimeter should be in. /// Returns DEFAULT if "not applicable" and there's no recommendation. /// </summary> /// <param name="vessel">The vessel for which we're recommending.</param> /// <param name="parachutes">The set of parachutes on the vessel.</param> /// <param name="previousState">The prior state of the altimeter.</param> /// <param name="reason">The reason for the recommendation. May be empty if the recommendation is the same as the previous state.</param> /// <returns></returns> private static AltimeterDisplayState RecommendAltimeterState( Vessel vessel, List <ModuleParachute> parachutes, AltimeterDisplayState previousState, out string reason) { reason = string.Empty; // Are we landed? switch (vessel.situation) { case Vessel.Situations.LANDED: case Vessel.Situations.PRELAUNCH: case Vessel.Situations.SPLASHED: reason = "on surface"; return(AutoAGLSettings.LandedSplashedPreference); } // Check for imminent terrain collision. double clearance = CurrentGroundClearance(vessel); double collisionThresholdSeconds = AutoAGLSettings.TerrainCollisionThresholdSeconds(Atmospheres(vessel)); if (!double.IsNaN(collisionThresholdSeconds)) { double timeAhead; double collisionSeconds = ProjectTimeUntilImpact(vessel, collisionThresholdSeconds, out timeAhead); if (collisionSeconds < collisionThresholdSeconds) { if (previousState != AltimeterDisplayState.AGL) { if (vessel.verticalSpeed < 0) { if (timeAhead > 0) { reason = string.Format( "< {0:0.0}s from terrain {1:0.0}s ahead, {2:0}m @ {3:0} m/s", collisionThresholdSeconds, timeAhead, clearance, -vessel.verticalSpeed); } else { reason = string.Format( "< {0:0.0}s from terrain, {1:0}m @ {2:0} m/s", collisionThresholdSeconds, clearance, -vessel.verticalSpeed); } } else { if (timeAhead > 0) { reason = string.Format( "< {0:0.0}s from terrain {1:0.0}s ahead, {2:0}m", collisionThresholdSeconds, timeAhead, clearance); } else { reason = string.Format( "< {0:0.0}s from terrain, {1:0}m", collisionThresholdSeconds, clearance); } } } return(AltimeterDisplayState.AGL); } } // Check for deployed chute double parachuteAltitude = double.NaN; if (AutoAGLSettings.ParachuteAltitudeMultiplier > 0) { parachuteAltitude = CalculateParachuteActivationAltitude(parachutes); if (clearance < parachuteAltitude * AutoAGLSettings.ParachuteAltitudeMultiplier) { if (previousState != AltimeterDisplayState.AGL) { reason = string.Format("< {0:0.0}x parachute's {1}m", AutoAGLSettings.ParachuteAltitudeMultiplier, parachuteAltitude); } return(AltimeterDisplayState.AGL); } } // None of the above apply, so prefer ASL by default. if (previousState != AltimeterDisplayState.ASL) { if (!double.IsNaN(collisionThresholdSeconds)) { reason = string.Format( "> {0:0.0}s from terrain, {1:0}m", collisionThresholdSeconds, clearance); } else if (!double.IsNaN(parachuteAltitude)) { reason = (parachuteAltitude > 0) ? string.Format("> {0:0.0}x parachute's {1}m", AutoAGLSettings.ParachuteAltitudeMultiplier, parachuteAltitude) : "no active chutes"; } else { reason = "all checks disabled"; } } return(AltimeterDisplayState.ASL); }
/// <summary> /// Called on each frame. /// </summary> public void LateUpdate() { // Make sure we have a vessel. Vessel vessel = FlightGlobals.ActiveVessel; if (vessel == null) { return; } // Keep track of any vessel changes we care about. UpdateVessel(vessel); // The stuff that comes after here may potentially involve doing // a fair amount of CPU work, so return without doing anything unless // it's time to check. DateTime now = DateTime.Now; if (now < nextUpdateTime) { return; } nextUpdateTime = now + UPDATE_INTERVAL; if (!AutoAGLSettings.ModEnabled) { return; } if (isGamePaused) { return; // no point in calculating if we're not flying } // To avoid thrashing, we never tinker with the altimeter unless it's been // a reasonable length of time since the last time the altimeter mode changed. if (now < earliestAllowableToggleTime) { return; } // Now we have to balance two possibly conflicting preferences. // On the one hand, this mod's logic will have an idea of what the altimeter setting // "should" be (automatically), based on ship altitude and parachutes and such. // On the other hand, it's possible that the user recently *manually* selected an // altimeter mode, by clicking on the tumbler, and we don't want to override the // user's manually expressed preference. // // Here's how we resolve this conflict: Whenever the user manually picks an altimeter // mode, we remember that in the userSelection variable, which is set either to ASL/AGL // (if the user has manually picked one of those) or DEFAULT (if the user hasn't picked // anything and we have free license to set it automatically). Here's how we decide: // // 1. If userSelection is DEFAULT, just set the altimeter to whatever the auto-decider // code wants it to be, and stop here. // 2. Otherwise, figure out what we *would* automatically set it to, if the user's // manual decision didn't constrain us. // 3. If the indicated auto-mode is different from the user's preference... do nothing // and leave the altimeter alone. // 4. If the indicated auto-mode *equals* the user's preference, then still do nothing // to the altimeter (since they're in agreement)... but reset the userSelection flag // to DEFAULT (meaning "no user preference"). // // Here's an example of how that works in practice. Let's say you're climbing from the // surface in AGL mode, and you *haven't* touched the altimeter toggle manually. At some // point you climb high enough that the mod automatically switches you to ASL mode. You then // decide you don't like that, and manually toggle it to AGL. At this point, your preference // is stored, so the mod won't override you. Later on, the ship descends again to the // point where it *would* set you to AGL if it were in auto mode. At that point, your // manually-expressed preference would get reset and the mod would revert to full auto, // so that if the ship then climbs *again*, it would auto-switch to ASL as needed. string reason; AltimeterDisplayState currentMode = AltitudeTumbler.Instance.CurrentMode; AltimeterDisplayState recommendedState = RecommendAltimeterState( vessel, parachutes, currentMode, out reason); if (userSelection == AltimeterDisplayState.DEFAULT) { // No user selection, so just set it if it needs setting. if (recommendedState != currentMode) { Logging.Log("Automatically switching altimeter to " + recommendedState + " (" + reason + ")"); AltitudeTumbler.Instance.SetModeTumbler(recommendedState); } return; } // The user picked something. Did they pick the same as what we automatically picked, or different? if (userSelection == recommendedState) { // It matches, so clear the user selection if it's time yet. Logging.Log("Transitioned to auto-" + recommendedState + " zone, clearing user selection"); userSelection = AltimeterDisplayState.DEFAULT; return; } // If we get here, it means that there's a user selection and it conflicts with the // auto-recommended state, so we should do nothing. }