Jump to content

xEvilReeperx

Members
  • Posts

    894
  • Joined

  • Last visited

Everything posted by xEvilReeperx

  1. Uh, if you're talking about the biome used in the science report they'll always match. If you're talking literal ExperimentSituations then the biome map doesn't play any part in that, only altitude (to determine whether the vessel is at ocean level or not, if the body has ocean). The slow tedious elevation check is why I was pushing for a target location. I think any manual entry would be a mistake and far more tedious, especially if you want to support the various planet modifications or the rarer case of edited biomes Edit: I see what you're saying. I'm not concerned about the "that doesn't really make sense" entries. I'm more concerned that we make sure the contract is indeed completable. If there's a location on the planet where you can SrfLanded and take a temperature measurement from Ocean, I would let it pass since it's feasible to do that crazy as it is. But if you take a contract for SrfSplashed in Desert and there's literally no place on the planet where you could possibly succeed, that's a problem
  2. You're right! I knew as soon as you mentioned it that I'd have to find out what you were talking about because this is exactly the kind of thing to go unnoticed for ages. I actually need to use the stock broken way for ScienceAlert in this case or the alerts won't work right but this is very good to know. It does mean I'm missing some alerts for your experiments though. I'll have to fix that. These aren't too bad but the problem is filtering out the "bad" auto-generated experiments. Is it possible for Contract Configurator to put a marker on the planet surface? If you selected a specific location (+radius of that point) on the planet it would be much simpler to identify "impossible" entries and avoid offering "check the temperature while splashed down in this desert"-type entries. Here's a quick brain barf: [KSPAddon(KSPAddon.Startup.Flight, false)] public class UtilTester : MonoBehaviour { private void Awake() { var subjectQuery = new ScienceSubjectQuery(); var visited = new VisitedCelestialBodyQuery(); var possibleResults = new PossibleExperimentResultsQuery(subjectQuery); var remainingScienceQuery = new RemainingScienceQuery(subjectQuery); var nextReportValueQuery = new NextScienceReportQuery(subjectQuery); print("Running tests"); // all possible subjects on planets we've visited and taken some science from var allPossibleResults = visited.Get() .SelectMany( cb => ResearchAndDevelopment.GetExperimentIDs() .Select(ResearchAndDevelopment.GetExperiment) .SelectMany(experiment => possibleResults.Get(experiment, cb))) .OrderBy(ss => ss.id) .ToList(); // return only those we've done so far var partials = allPossibleResults .Where(scienceSubject => scienceSubject.science > 0f); // return only those that haven't been done at all var unresearched = allPossibleResults.Where(subject => Mathf.Approximately(subject.science, 0f)); // return those worth at least X science var worthAtLeastX = allPossibleResults.Where(subject => remainingScienceQuery.Get(subject) > 25f); // return the best value orbital experiments (of planets we've already visited) var bestOrbitalValue = allPossibleResults .Where( subject => subject.IsFromSituation(ExperimentSituations.InSpaceHigh) || subject.IsFromSituation(ExperimentSituations.InSpaceLow)) .OrderByDescending(subject => remainingScienceQuery.Get(subject)); foreach (var ov in bestOrbitalValue) print("You could " + ov.id + " for " + remainingScienceQuery.Get(ov) + ", next report will be worth " + nextReportValueQuery.Get(ov)); } } // because ScienceExperiments are related to certain subjects and the ScienceSubject only contains // a string to identify it ... we could split the string but how ugly is that? public class PossibleScienceSubject { public ScienceSubject Subject { get; private set; } public ScienceExperiment Experiment { get; private set; } public float scienceCap { get { return Subject.scienceCap; } } public float science { get { return Subject.science; } } public string id { get { return Subject.id; } } public PossibleScienceSubject(ScienceSubject subject, ScienceExperiment experiment) { Subject = subject; Experiment = experiment; } public bool IsFromSituation(ExperimentSituations situation) { return Subject.IsFromSituation(situation); } } // The purpose of this query is to avoid putting empty entries into R&D building by accident public class ScienceSubjectQuery { public PossibleScienceSubject Get(ScienceExperiment experiment, ExperimentSituations situation, CelestialBody body, string biome) { var defaultIfNotResearched = new ScienceSubject(experiment, situation, body, biome); return new PossibleScienceSubject(ResearchAndDevelopment.GetSubjects() .SingleOrDefault(researched => defaultIfNotResearched.id == researched.id) ?? defaultIfNotResearched, experiment); } } // just about does what it says public class NextScienceReportQuery { private readonly ScienceSubjectQuery _subjectQuery; public NextScienceReportQuery(ScienceSubjectQuery subjectQuery) { if (subjectQuery == null) throw new ArgumentNullException("subjectQuery"); _subjectQuery = subjectQuery; } public float Get(ScienceExperiment experiment, CelestialBody body, ExperimentSituations situation, string biome, float xmitScalar = 1f) { return ResearchAndDevelopment.GetScienceValue( experiment.baseValue * experiment.dataScale, _subjectQuery.Get(experiment, situation, body, experiment.BiomeIsRelevantWhile(situation) ? biome : string.Empty).Subject, xmitScalar) * HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; } public float Get(ScienceExperiment experiment, ScienceSubject subject, float xmitScalar) { return ResearchAndDevelopment.GetScienceValue( experiment.baseValue * experiment.dataScale, subject, xmitScalar) * HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; } public float Get(PossibleScienceSubject possibleSubject, float xmitScalar = 1f) { return Get(possibleSubject.Experiment, possibleSubject.Subject, xmitScalar); } } public class RemainingScienceQuery { private readonly ScienceSubjectQuery _subjectQuery; public RemainingScienceQuery(ScienceSubjectQuery subjectQuery) { if (subjectQuery == null) throw new ArgumentNullException("subjectQuery"); _subjectQuery = subjectQuery; } public float Get(ScienceExperiment experiment, ExperimentSituations situation, CelestialBody body, string biome) { var subject = _subjectQuery.Get(experiment, situation, body, biome); return Get(subject); } public float Get(PossibleScienceSubject possibleSubject) { return possibleSubject.scienceCap * HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier - possibleSubject.science; } } // return bodies which have at least one non-zero (transmitted or recovered) science result public class VisitedCelestialBodyQuery { public IEnumerable<CelestialBody> Get() { return FlightGlobals.Bodies .Where(cb => ResearchAndDevelopment.GetSubjects() .Any(ss => ss.IsFromBody(cb) && ss.science > 0f)); } } public class PossibleExperimentResultsQuery { private readonly ScienceSubjectQuery _subjectQuery; public PossibleExperimentResultsQuery(ScienceSubjectQuery subjectQuery) { if (subjectQuery == null) throw new ArgumentNullException("subjectQuery"); _subjectQuery = subjectQuery; } public IEnumerable<PossibleScienceSubject> Get(ScienceExperiment experiment, CelestialBody body) { var situations = Enum.GetValues(typeof (ExperimentSituations)).Cast<ExperimentSituations>(); return situations .Where(sit => experiment.IsAvailableWhile(sit, body)) .SelectMany(sit => { var biomesPlusKsc = (experiment.BiomeIsRelevantWhile(sit) ? ResearchAndDevelopment.GetBiomeTags(body).ToArray() : Enumerable.Empty<string>()).ToList(); var biomes = biomesPlusKsc.Where( biome => body.BiomeMap != null && body.BiomeMap.Attributes.Any(attr => attr.name.Replace(" ", string.Empty) == biome)) .ToList(); var kscStatics = biomesPlusKsc.Except(biomes); return (biomesPlusKsc.Any() ? biomes : new List<string>{string.Empty}) .Select(biome => _subjectQuery.Get(experiment, sit, body, biome)) .Union(sit == ExperimentSituations.SrfLanded // static KSC items can only be landed on as far as I know ? kscStatics.Select( staticName => _subjectQuery.Get(experiment, ExperimentSituations.SrfLanded, body, staticName)) : Enumerable.Empty<PossibleScienceSubject>()); }); } } It still has some problems like asking for splashed down experiments in the desert and not being hugely tested. If we can't choose a specific location, it might be possible to roughly determine which biomes aren't covered by ocean (I think stupid_chris had some PQS height code that could help with this) which would eliminate most false positives Edit: I just realized there's a small bug in that last query that would prevent experiments with nonrelevant biomes from working correctly; updated code
  3. Doesn't look like it's accurate based off that description. It's possible it was changed since the wiki was edited, but as far as I know it's been this way since ScienceAlert was created public class VesselSituationDumper : MonoBehaviour { private Rect _rect = new Rect(0, 0, 300f, 100f); private void OnGUI() { _rect = GUILayout.Window(342323, _rect, DrawWindow, "Situation Monitor"); } private void DrawWindow(int winid) { GUILayout.Label("Vessel Situation: " + FlightGlobals.ActiveVessel.situation); GUILayout.Label("Experiment Situation: " + ScienceUtil.GetExperimentSituation(FlightGlobals.ActiveVessel)); GUI.DragWindow(); } }[KSPAddon(KSPAddon.Startup.Flight, false)]
  4. Thanks for reminding me. The old version should still work but I recompiled anyway just in case
  5. Could you elaborate on this? If it worked like that, ScienceAlert would be horribly broken. I just tested aerobraking in Duna's atmosphere and the vessel situation is definitely "flying" but now you have me concerned that it's broken in a non-obvious way which has gone unreported Experiment crewReport@DunaFlyingHigh just became available! Total potential science onboard currently: 0 (Cap is 25, threshold is Unresearched, current sci is 0, expected next report value: 25)
  6. Internal space GameObjects are on an separate layer (16) not normally rendered by the flight camera. I assume you could re-export the IVA view from Unity as a regular model on the correct layer and your problem would be fixed (I haven't done any IVA stuff at all so I'm not certain you can do this), otherwise a small plugin to adjust either the GameObject layers or to include layer 16 in the flight camera's culling mask would be needed
  7. Are you on a non-windows platform? Just dropping the System.Core into the same directory as the assembly with code from the OP worked fine for me on Windows 7 but there might not be an equivalent of named pipes on other platforms
  8. This isn't exactly true but you might need to write extra framework get it to do what you want. What do you mean by "make it work differently"?
  9. Yes absolutely. UIPartActionFloatRange is a component that contains the logic for the slider used for KSPFields marked with the UI_FloatRange attribute. When a UIPartActionWindow is created, each PartModule field is checked for attributes that determine whether or not and how that field gets displayed in the part popup menu; in this case, we're modifying the prefab that will be used for PartModule fields with the UI_FloatRange attribute.
  10. Probably not the same trick. On a cursory glance, I'm seeing a very interesting new interface called IPartSizeModifier. That might be an excellent candidate for shameless abuse if it can be made to accept negative values
  11. Eww. Rip out that ugly if else code and replace the entire block with Vessel.GetLandedAtString. My version: public static string GetCurrentBiome() { if (FlightGlobals.ActiveVessel != null) if (FlightGlobals.ActiveVessel.mainBody.BiomeMap != null) return !string.IsNullOrEmpty(FlightGlobals.ActiveVessel.landedAt) ? Vessel.GetLandedAtString(FlightGlobals.ActiveVessel.landedAt) : ScienceUtil.GetExperimentBiome(FlightGlobals.ActiveVessel.mainBody, FlightGlobals.ActiveVessel.latitude, FlightGlobals.ActiveVessel.longitude); return string.Empty; } My condolences on time spent running around KSC
  12. Found it! A small oversight in the options window prevented the filter settings from being updated when loading a new profile. The correct setting was being applied to the actual alert system, though. Another small bug was in the NotMaxed filter: for whatever reason, I set it at <98% instead of "next report worth non-zero" like it should be so that'll be fixed. There's still something off. You should've received an alert for the report at 1:46 for sure. I've run out of time right now but I'll go over it tonight with a fine tooth comb to see if the calculation is wrong somewhere.
  13. It's a good suggestion. I spent about 20 minutes poking at it and came up with this: [KSPAddon(KSPAddon.Startup.EditorAny, true)] public class CtrlClickEditNumber : MonoBehaviour { private const string ControlLockId = "InputValueDialogLock"; private UIPartActionFloatRange paFloatRange; private void Start() { DontDestroyOnLoad(this); paFloatRange = UIPartActionController.Instance.fieldPrefabs.FirstOrDefault( fi => fi.GetType() == typeof(UIPartActionFloatRange)) as UIPartActionFloatRange; if (paFloatRange == null) { Debug.LogError("Failed to find a UI prefab. Post a bug report"); return; } paFloatRange.gameObject.PrintComponents(new DebugLog("FloatRange")); paFloatRange.gameObject.AddComponent<ControlClickEntersEditMode>(); } } class ControlClickEntersEditMode : MonoBehaviour { private const string ControlLockId = "FloatSliderEditModeLock"; private const float ColorChangePerSecond = 2f; private static Color FlashColor = new Color(255f, 255f, 255f); private static char[] ValidCharacters = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', ',', '.'}; private Material _backgroundMaterial; private Color _originalBackgroundColor; private Gradient _gradient; private UIPartActionFieldItem _fieldItem; private UIProgressSlider _slider; private UIButton _button; private SpriteText _ourSpriteText; private SpriteText _originalSpriteText; private bool _editMode = false; private string _inputString = string.Empty; private float _initialValue = 0f; private void Start() { print("ControlClickEntersEditMode instantiated"); if (!LookupComponents()) { print("ControlClickEntersEditMode: failed to find a dependency"); Destroy(this); return; } // the game sets the original sprite text every frame, overwriting any changes. Instead of // playing LateUpdate shenanigans and fighting it, clone that component and then we can // activate/deactive our custom text as needed var clone = Instantiate(_originalSpriteText.gameObject, _originalSpriteText.transform.position, _originalSpriteText.transform.rotation) as GameObject; _ourSpriteText = clone.GetComponent<SpriteText>(); _ourSpriteText.Text = "<not set>"; clone.SetActive(false); clone.transform.parent = _originalSpriteText.transform.parent; clone.layer = _originalSpriteText.gameObject.layer; _gradient = new Gradient(); _gradient.SetKeys( new[] { new GradientColorKey(_originalBackgroundColor, 0f), new GradientColorKey(FlashColor, 1f) }, new[] {new GradientAlphaKey(.5f, 0f), new GradientAlphaKey(.9f, 1f)}); _button.AddValueChangedDelegate(OnSliderClick); _initialValue = _slider.Value; } private bool LookupComponents() { _fieldItem = gameObject.GetComponent<UIPartActionFieldItem>(); if (_fieldItem == null) { print("ERROR: Couldn't find UIPartActionFieldItem on " + gameObject.name); return false; } _slider = GetComponentsInChildren<UIProgressSlider>(true).FirstOrDefault(); if (_slider == null) { print("ERROR: Couldn't find UIProgressSlider"); return false; } _slider.AddValueChangedDelegate(OnSliderClick); _button = GetComponentsInChildren<UIButton>(true).FirstOrDefault(); if (_button == null) { print("ERROR: Couldn't find UIButton"); return false; } var background = transform.Find("Background"); if (background == null) { print("ERROR: Couldn't find Background transform"); return false; } _backgroundMaterial = background.renderer.material; // this causes the material to be clone _originalBackgroundColor = _backgroundMaterial.color; // the game seems to set the text value of this every frame. Instead of fighting with // it or playing LateUpdate shenanigans, we'll just clone it and hide the original // when needed _originalSpriteText = GetComponentsInChildren<SpriteText>(true).FirstOrDefault(st => st.name == "amnt"); if (_originalSpriteText == null) { print("ERROR: couldn't find SpriteText"); return false; } return true; } private void OnDestroy() { print("ControlClickEntersEditMode destroying"); Destroy(_backgroundMaterial); _slider.RemoveValueChangedDelegate(OnSliderClick); _button.RemoveValueChangedDelegate(OnSliderClick); } private void OnSliderClick(IUIObject obj) { if (Input.GetKey(KeyCode.LeftControl)) SetEditMode(true); } private void SetFieldValue(float value) { _fieldItem.Field.SetValue(value, _fieldItem.Field.host); _ourSpriteText.Text = value.ToString(new NumberFormatInfo()); } private void Update() { if (!_editMode) return; if (Input.GetKeyDown(KeyCode.Escape)) { _inputString = _initialValue.ToString("F2"); SetEditMode(false); } else if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter)) { if (!IsNumeric(_inputString)) { Debug.LogError("Couldn't set slider value because '" + _inputString + "' is nonnumeric"); SetFieldValue(_initialValue); } else SetFieldValue(float.Parse(_inputString, NumberStyles.Float)); SetEditMode(false); } else if (Input.anyKeyDown) { if (ValidCharacters.Contains(Event.current.character)) { _inputString += Event.current.character; } else if (Input.GetKeyDown(KeyCode.Backspace)) { if (_inputString.Length > 0) _inputString = _inputString.Substring(0, _inputString.Length - 1); } _ourSpriteText.Text = _inputString; } } private bool IsNumeric(string str) { float result; return float.TryParse(str, NumberStyles.Float, new NumberFormatInfo(), out result); } private void SetEditMode(bool tf) { if (tf) { if (!_editMode) { _editMode = true; _originalSpriteText.gameObject.SetActive(false); _ourSpriteText.gameObject.SetActive(true); _inputString = _fieldItem.Field.GetValue<float>(_fieldItem.Field.host).ToString("F2"); InputLockManager.SetControlLock(ControlTypes.All, ControlLockId); StartCoroutine("FlashySlider"); } } else { _editMode = false; _originalSpriteText.gameObject.SetActive(true); _ourSpriteText.gameObject.SetActive(false); InputLockManager.RemoveControlLock(ControlLockId); } } private IEnumerator FlashySlider() { float delta = 0f; while (_editMode) { delta = UtilMath.WrapAround(delta + Time.deltaTime*ColorChangePerSecond, 0f, 2f); _backgroundMaterial.color = _gradient.Evaluate(delta < 1f ? delta : 2f - delta); yield return 0; } _backgroundMaterial.color = _originalBackgroundColor; } } It needs polish and support for the other slider types but it's a working prototype to get you started (Ctrl+click on a slider to enter edit mode, escape cancels, enter confirms). I tested it on the engine thrust limit slider
  14. Does your log mention any errors? Could you describe exactly which steps you take? Newly spawned vessels will get the "default" profile (which you can customize by overwriting) but after that, any changes you make to the vessel's settings should persist. The following would help me: Start a new career game Launch a mk1pod Open ScienceAlert settings and change anything, such as the "min science" slider. Just move it to a non-zero position The profile name should now be *default* Quicksave, then quickload Is the slider value correct? If not, logs + persistence file would help and stop here. Click "save" and accept the overwrite prompt Profile name should now be "default" with no stars Quicksave, then quickload Is the slider still correct? If not, logs + the profiles.cfg in ScienceAlert folder if it exists
  15. The SCANsat science and kerbal duplicate bug are on my list of items to squash tonight so hopefully I'll have a fix for you guys tomorrow. Thanks for the reports and details. Edit: 1.8.7 now released which should fix both issues
  16. The simplest way would be to dynamically increase the part limit instead. Just glancing at it, I'd say actually preventing the part from being included in the count would exceed the effort : reward ratio for your mod [KSPAddon(KSPAddon.Startup.MainMenu, true)] public class ExtendPartLimitCount : GameVariables { private void Awake() // important that this be in Awake { // just a little future-proofing if (Instance.GetType() != typeof (GameVariables)) { Debug.LogError(string.Format("{0} cannot run; conflicting GameVariables instance of type {1}", GetType().Name, Instance.GetType().FullName)); Destroy(this); return; } Instance = this; DontDestroyOnLoad(this); } public override int GetPartCountLimit(float editorNormLevel) { var baseLimit = base.GetPartCountLimit(editorNormLevel); if (!HighLogic.LoadedSceneIsEditor) return baseLimit; return baseLimit + EditorLogic.fetch.ship.parts .Count(p => p.GetComponent<ExtendPartCountLimitModule>() != null); } } public class ExtendPartCountLimitModule : PartModule { }
  17. Edit in what way? Basically yes (the prefabs are exposed), but working with EzGUI by hand is tedious. If you had some idea of what you wanted to do it would be helpful
  18. Merely replace GameVariables.Instance with a derived version that overrides the methods the modder wants to change. Say I wanted to always allow EVAs: [KSPAddon(KSPAddon.Startup.MainMenu, true)] public class EVAAnytime : GameVariables { private void Awake() // important that this be in Awake { GameVariables.Instance = this; DontDestroyOnLoad(this); } public override bool UnlockedEVA(float astroComplexNormLevel) { return true; } }
  19. Yes, I'm definitely interested. It'll help pinpoint exactly where to look for the problem I experimented with this way back when first starting ScienceAlert but the problem is that it doesn't quite fit within this mod's scope. I like the concept for a separate mod but it's a matter of available free time I'm afraid
  20. Thanks for the report and video. Does your log mention any exceptions? I thought I'd squished this bug
  21. Can you check which version you're running? If it's 1.8.6, your log and some reproduction steps would be much appreciated
  22. This is odd. Are you certain you haven't somehow got two installs? Everything for ScienceAlert will be under /GameData/ScienceAlert normally; however, my plugins will work even with renamed (top-level) folders or moved to wrong locations so it's possible you might have accidentally extracted the archive into another mod's folder or something like that Otherwise I'd be interested in having a look at your log
  23. Hi, I mentioned in the other thread to let me know if TextureReplacer wasn't good enough. Keep in mind, we do this on our free time and I have currently 37 personal mod projects, most unreleased, to manage in addition to regular not-ksp-modding things so I have to budget my time. It was never released as its own mod because it has been forever planned as a feature of EnhancedNavball
  24. Sorry I'm coming up with a comprehensive "test these things" list for the rewrite so hopefully nasty bugs like that are much less likely to get through to releases
  25. I experimented with this in 0.90 but had to prioritize my time a bit so nothing ever came of it. It still works in 1.0. I'm just going to barf it up in hopes it'll be useful [KSPAddon(KSPAddon.Startup.MainMenu, true)] public class Test_ChangeSpaceCenterBuildingsExternal : MonoBehaviour { private void Start() { Log.Debug("Testing building external changes"); var facility = FlightGlobals.Bodies.Where(cb => cb.isHomeWorld) .SelectMany(cb => cb.gameObject.GetComponentsInChildren<Upgradeables.UpgradeableFacility>(true)) .FirstOrDefault(f => f.id == "SpaceCenter/VehicleAssemblyBuilding"); if (facility.IsNull()) throw new InvalidOperationException("facility"); Log.Normal("UpgradeableFacility:"); facility.gameObject.PrintComponents(); var model = GameDatabase.Instance.databaseModel.Find( go => go.name == "BuildYourOwnSpaceCenter/structures/TestCube/TestCube"); if (model == null) Log.Error("failed to find model!"); model.AddComponent<TestScale>(); model.AddComponent<CrashObjectName>().objectName = "That ugly box"; model.SetLayerRecursive(15); model.SetActive(true); model.transform.parent = facility.GetUpgradeLevels()[0].facilityPrefab.transform; model.transform.localPosition = Vector3.zero; DontDestroyOnLoad(model); var uft = facility.gameObject.AddComponent<UpgradeableFacilityTweakable>(); uft.ImpersonateFacility(facility); uft.SetPrefab(0, model); Destroy(facility); } } class UpgradeableFacilityTweakable : UpgradeableFacility { public void ImpersonateFacility(UpgradeableFacility target) { upgradeLevels = target.GetUpgradeLevels(); facilityTransform = target.GetFacilityTransform(); setup = false; preCompiledId = true; id = target.id; facilityLevel = target.FacilityLevel; SetupLevels(); for (int i = 0; i < upgradeLevels.Length; ++i) upgradeLevels[i].levelCost = 1000f * i; } public void SetPrefab(int level, GameObject prefab) { upgradeLevels[level].facilityPrefab = prefab; setup = false; SetupLevels(); } } Those are the main pieces. The interiors are simpler (you can just swap the model right out). Not a complete solution (the structure can't be destroyed -- haven't investigated that yet) but should jumpstart anyone willing to make the effort
×
×
  • Create New...