xEvilReeperx
Members-
Posts
894 -
Joined
-
Last visited
Content Type
Profiles
Forums
Developer Articles
KSP2 Release Notes
Everything posted by xEvilReeperx
-
Well ... you could add a fake renderer that will be controlled by the stock highlighting code in Part, then copy its material properties to all your other renderers with a strategically placed MonoBehaviour. That's the best I could come up with that will cover all the edge cases (such as when the editor logic changes highlight colors or freezes Parts). Proof of concept: [KSPAddon(KSPAddon.Startup.MainMenu, true)] class AddHighlightingFix : MonoBehaviour { public const string DummyTransformName = "dummy_highlighter_dont_delete"; private void Start() { DontDestroyOnLoad(this); GetAllPartModelTransforms() .ToList() .ForEach(AddHighlightCopierAndDummyRenderer); } // note: it's important to avoid adding our MonoBehaviour directly to the Part's GO! The game will strip it out // on root parts if it isn't one of the allowed types (PartModule in particular but there are some others) private static IEnumerable<Transform> GetAllPartModelTransforms() { return PartLoader.LoadedPartsList.Where(ap => ap.partPrefab.transform.Find("model") != null) .Select(ap => ap.partPrefab.transform.Find("model")); } private static void AddHighlightCopierAndDummyRenderer(Transform partModelTransform) { partModelTransform.gameObject.AddComponent<CopyHighlightFromDummyRenderer>(); var dummyContainer = new GameObject(DummyTransformName); dummyContainer.transform.parent = partModelTransform; DontDestroyOnLoad(dummyContainer); dummyContainer.AddComponent<MeshRenderer>(); } } class CopyHighlightFromDummyRenderer : MonoBehaviour { private Renderer _dummyRenderer; private readonly MaterialPropertyBlock _propertyBlock = new MaterialPropertyBlock(); private List<Renderer> _allOtherRenderers = new List<Renderer>(); private void Awake() { var dummyTransform = transform.Find(AddHighlightingFix.DummyTransformName); if (!HighLogic.LoadedSceneIsEditor || dummyTransform == null || dummyTransform.renderer == null) { Destroy(this); return; } _dummyRenderer = dummyTransform.renderer; } private void Start() { OnTransformChildrenChanged(); } private void OnTransformChildrenChanged() { _allOtherRenderers = GetComponentsInChildren<Renderer>(true).Except(new[] { _dummyRenderer }).ToList(); } private void LateUpdate() { // grab current highlight colors _propertyBlock.SetColor(HighLogic.ShaderPropertyID_RimColor, _dummyRenderer.material.GetColor(HighLogic.ShaderPropertyID_RimColor)); _propertyBlock.SetFloat(HighLogic.ShaderPropertyID_RimFalloff, _dummyRenderer.material.GetFloat(HighLogic.ShaderPropertyID_RimFalloff)); // apply to all other renderers bool refreshChildren = false; foreach (var r in _allOtherRenderers) { if (r == null) // if somebody destroyed a Renderer, we'll want to refresh the list { refreshChildren = true; continue; } r.SetPropertyBlock(_propertyBlock); } if (refreshChildren) OnTransformChildrenChanged(); } }
-
Iterating through the MapAttributes works fine for the most part, with the only wrinkle being that it'll miss any "special" biomes that are hard-coded and not on the biome map like the KSC. If you're generating possible subjectids, you'll want to use ResearchAndDevelopment.GetBiomeTags() to include those as well The results are stored in the ScienceExperiment itself as a dictionary with (half of) the subjectid as key foreach (var experiment in ResearchAndDevelopment.GetExperimentIDs().Select(ResearchAndDevelopment.GetExperiment)) foreach (var result in experiment.Results) { // [subjectid] : [result text] print(result.Key + " : " + result.Value); } // crew report default : You record the crew's assessment of the situation. KerbinSrfLandedLaunchpad : We don't seem to be moving very fast right now. KerbinSrfLandedRunway : Reporting in at the Runway. Good thing there's not a lot of air traffic, because I don't think we ever got clearance from the tower to be here. KerbinSrfLandedKSC : This is our Space Center here. We're home. KerbinFlyingLowGrasslands : Hey I can see my house from here, I think. KerbinFlyingLowGrasslands* : It's very comforting to see that much green below you. // snip you get the idea Edit: I just realized the asterisk is for marking results for subjectids that don't exactly match, for instance "BopSrfLanded*" will match any subjectid that contains BopSrfLanded in it. Multiple asterisk-marked entries are randomly selected by ResearchAndDevelopment.GetResults()
-
Disable the particle emitter in editor
xEvilReeperx replied to flywlyx's topic in KSP1 C# Plugin Development Help and Support
Looks like it's an oversight in KSP. Particle[Emitter/Animator/Renderer] components work with Icon_Hidden but KSPParticleEmitter and probably SkinnedMeshRenderer do not. The Icon_Only tag functions normally. Well, easily fixed in code: [KSPAddon(KSPAddon.Startup.Instantly, true)] public class FixIconHiddenBug : LoadingSystem { private static bool _hasRun = false; private const string IconHiddenTag = "Icon_Hidden"; private void Start() { if (_hasRun) { Destroy(gameObject); return; } FindObjectOfType<LoadingScreen>().loaders.Add(this); } public override bool IsReady() { return _hasRun; } public override void StartLoad() { _hasRun = true; var startTime = Time.realtimeSinceStartup; foreach ( var iconGo in PartLoader.LoadedPartsList.Where(ap => ap.iconPrefab != null).Select(ap => ap.iconPrefab)) { PartLoader.StripTaggedTransforms(iconGo.transform, IconHiddenTag); } print("Finished fixing tags in " + (Time.realtimeSinceStartup - startTime).ToString("F3") + " sec"); } public override string ProgressTitle() { return "Fixing icon tags"; } } Edit: Just so nobody is surprised by it, this will delete any tagged GO and its children -
Disable the particle emitter in editor
xEvilReeperx replied to flywlyx's topic in KSP1 C# Plugin Development Help and Support
Add the Icon_Hidden tag to the particle emitter's GameObject inside Unity before exporting it -
Couple of c# / API questions.
xEvilReeperx replied to TheMightySpud's topic in KSP1 C# Plugin Development Help and Support
You'll save yourself a lot of time if you can get debug mode working inside KSP via one of the stickies. Where is the cfg file itself located? That would be my current suspicion. GameDatabase only auto-loads ConfigNodes inside GameData and subdirectories so if you've put it elsewhere (maybe alongside the persistent sfs or .craft you copied its contents from?) it won't be found- 28 replies
-
It's possible and even probable that the save you loaded up with Kopernicus isn't easily recoverable but the rest should be salvageable. We'll need your logs to even begin to guess at what has broken them though. Edit: Actually I'll just take a swing since it's likely: if the game crashed while saving, it's possible the persistent.sfs of the game you loaded when Kopernicus installed was corrupted and KSP is throwing an unhandled exception while trying to parse it. This will cause a chain reaction of failure: most likely, clicking "Resume game" will appear to do nothing at all while the log (Alt+F2) prints an exception Try moving or deleting that save's folder
-
This is the best solution I could come up with: [KSPScenario(ScenarioCreationOptions.AddToAllGames, GameScenes.FLIGHT)] public class ScenarioHideTheNut : ScenarioModule { public override void OnSave(ConfigNode node) { base.OnSave(node); foreach (var pps in HighLogic.CurrentGame.flightState.protoVessels.SelectMany(pv => pv.protoPartSnapshots)) pps.modules.RemoveAll( ppms => (ppms.moduleRef != null && ppms.moduleRef.GetType() == typeof (ModuleNut)) || ppms.moduleName == typeof (ModuleNut).Name); } } public class ModuleNut : PartModule { [KSPEvent(guiName = "Hello world", active = true, guiActive = true)] public void HelloWorld() { print("Nut says, \"Hello world!\""); } } public class AddNutToAllParts : VesselModule { private void Start() { if (!HighLogic.LoadedSceneIsFlight) return; var vessel = GetComponent<Vessel>(); foreach (var p in vessel.parts) if (!p.gameObject.GetComponents<ModuleNut>().Any()) { print("Adding " + typeof (ModuleNut).Name + " to " + p.partInfo.name); p.AddModule(typeof (ModuleNut).Name); } } } The ScenarioModule will prevent any PartModule(s) you want from being saved to the ConfigNode without preventing them from working normally otherwise. I used a VesselModule to add the test PartModule to the parts in the flight scene but it should work with MM scripts as well* if one needed to hide those Edit: *It occurs to me that you'd probably want to make sure your MM patch ran last to avoid those mismatch problems if you weren't adding PartModules on the fly
-
If there were an event that allowed you to intercept (and potentially veto) your target KSPEvent(s), would you still need to define your own? That's how I'd handle this. If you never need any more BaseFields than what your target modules implement, you can get away with just hijacking them and avoid being a PartModule altogether
-
Finding the orbit under the mouse?
xEvilReeperx replied to zentarul's topic in KSP1 C# Plugin Development Help and Support
[KSPAddon(KSPAddon.Startup.Flight, false)] public class OrbitMouseOver : MonoBehaviour { private bool _mouseOver = false; private PatchedConics.PatchCastHit _mousedOverPatch; private Rect _popup = new Rect(0f, 0f, 120f, 60f); private Texture2D _cross; private Rect _crossRect; private void Awake() { _popup.center = new Vector2(Screen.width * 0.5f - _popup.width * 0.5f, Screen.height * 0.5f - _popup.height * 0.5f); _cross = new Texture2D(32, 32, TextureFormat.ARGB32, false); _crossRect = new Rect(0, 0, _cross.width, _cross.height); var pixels = Enumerable.Repeat(Color.clear, _cross.width * _cross.height).ToArray(); for (int i = 0; i < _cross.width; ++i) for (int j = 0; j < _cross.height; ++j) { if (i == j || j == (_cross.width - i)) pixels[j * _cross.height + i] = Color.red; } _cross.SetPixels(pixels); _cross.Apply(); } private void Update() { _mouseOver = MouseOverOrbit(out _mousedOverPatch); if (!_mouseOver) return; var updatedLocation = _mousedOverPatch.GetUpdatedScreenPoint(); _popup.center = _crossRect.center = new Vector2(updatedLocation.x, Screen.height - updatedLocation.y); } private void OnGUI() { if (!_mouseOver) return; GUI.skin = HighLogic.Skin; GUILayout.BeginArea(GUIUtility.ScreenToGUIRect(_popup)); GUILayout.Label("UT: " + KSPUtil.PrintTime((int)(Planetarium.GetUniversalTime() - _mousedOverPatch.UTatTA), 5, true)); GUILayout.EndArea(); if (Event.current.type == EventType.Repaint) Graphics.DrawTexture(_crossRect, _cross); } private bool MouseOverOrbit(out PatchedConics.PatchCastHit hit) { hit = default(PatchedConics.PatchCastHit); if (FlightGlobals.ActiveVessel == null) return false; var patchRenderer = FlightGlobals.ActiveVessel.patchedConicRenderer; if (patchRenderer == null) return false; var patches = patchRenderer.solver.maneuverNodes.Any() ? patchRenderer.flightPlanRenders : patchRenderer.patchRenders; return PatchedConics.ScreenCast(Input.mousePosition, patches, out hit); } } Here's a rough proof of concept. It works on the active vessel's orbit. If you want to include the other orbits (CelestialBodies at least, anyway), you'll probably want to use OrbitRenderer.OrbitCast. I didn't go far on that path other than yes/no type mouseover debug spam since it became apparent that the maneuver node placement was doing something different and the active vessel's orbit wasn't included by that method -
32 minutes to load game
xEvilReeperx replied to SickSix's topic in KSP1 Technical Support (PC, modded installs)
It might be the network adapter issue. Try disabling any unused ones (Hamachi is the most likely culprit but there are other possibilities) and see if that helps -
The editor isn't using pre-rendered icons; there's actually a separate layer where instances of the icon are being rendered (think of the way you're looking at actual pieces of candy in a vending machine). If you wanted to do things with them like rotate or play animations, you'd need to set up a camera and do something similar. If you just need a static image, I've adapted some code from another of my projects that should get you there: [KSPAddon(KSPAddon.Startup.SpaceCentre, false)] public class PartIconSnapshot : MonoBehaviour { private const int IconWidth = 256; private const int IconHeight = 256; private Rect _rect = new Rect(0f, 0f, 120f, 120f); private Texture2D _selectedIcon = new Texture2D(1, 1); private int _partIndex = 0; private void Start() { _rect.center = new Vector2(Screen.width * 0.5f, Screen.height * 0.5f); CreatePartIconForCurrentPart(); } private void OnGUI() { _rect = KSPUtil.ClampRectToScreen(GUILayout.Window(GetInstanceID(), _rect, DrawWindow, "PartIconSnapshot")); } private void DrawWindow(int winid) { GUILayout.BeginHorizontal(); { if (GUILayout.Button("<<")) PreviousIcon(); if (GUILayout.Button(">>")) NextIcon(); GUILayout.FlexibleSpace(); GUILayout.Label(PartLoader.LoadedPartsList[_partIndex].name, GUILayout.ExpandWidth(true)); } GUILayout.EndHorizontal(); var textureRect = GUILayoutUtility.GetRect(IconWidth, IconHeight, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false)); if (Event.current.type == EventType.Repaint) Graphics.DrawTexture(textureRect, _selectedIcon); GUI.DragWindow(); } private void PreviousIcon() { if (--_partIndex < 0) _partIndex = PartLoader.LoadedPartsList.Count - 1; CreatePartIconForCurrentPart(); } private void NextIcon() { if (++_partIndex >= PartLoader.LoadedPartsList.Count) _partIndex = 0; CreatePartIconForCurrentPart(); } private void CreatePartIconForCurrentPart() { CreatePartIconSnapshot(PartLoader.LoadedPartsList[_partIndex]); } private void CreatePartIconSnapshot(AvailablePart part) { if (part == null) throw new ArgumentNullException("part"); Destroy(_selectedIcon); _selectedIcon = PartIconGenerator.Create2D(part, IconWidth, IconHeight, Quaternion.AngleAxis(-15f, Vector3.right) * Quaternion.AngleAxis(-30f, Vector3.up), Color.clear); } } public static class PartIconGenerator { private const string IconHiddenTag = "Icon_Hidden"; private const string KerbalEvaSubstring = "kerbal"; private static readonly int GameObjectLayer = LayerMask.NameToLayer("PartsList_Icons"); // note to future: if creating icons inside editor, you might want to choose a different layer or translate the camera and object out of frame private static Camera CreateCamera(int pixelWidth, int pixelHeight, Color backgroundColor) { var camGo = new GameObject("PartIconGenerator.Camera", typeof (Camera)); var cam = camGo.camera; cam.enabled = false; cam.cullingMask = (1 << GameObjectLayer); cam.clearFlags = ~CameraClearFlags.Nothing; cam.nearClipPlane = 0.1f; cam.farClipPlane = 10f; cam.orthographic = true; cam.backgroundColor = backgroundColor; cam.aspect = pixelWidth / (float) pixelHeight; // Camera Size = x / ((( x / y ) * 2 ) * s ) cam.orthographicSize = pixelWidth / (((pixelWidth / (float) pixelHeight) * 2f) * pixelHeight); cam.pixelRect = new Rect(0f, 0f, pixelWidth, pixelHeight); return cam; } private static Light CreateLight() { var light = new GameObject("PartIconGenerator.Light").AddComponent<Light>(); light.type = LightType.Directional; light.color = XKCDColors.OffWhite; light.cullingMask = 1 << GameObjectLayer; light.intensity = 0.5f; return light; } private static GameObject CreateIcon(AvailablePart part) { // kerbalEVA doesn't seem to init at origin if we aren't explicit var go = Object.Instantiate(part.iconPrefab, Vector3.zero, Quaternion.identity) as GameObject; // The kerbals are initially facing along positive Z so we'll be looking at their backs if we don't // rotate them around if (part.name.StartsWith(KerbalEvaSubstring)) go.transform.rotation = Quaternion.AngleAxis(180f, Vector3.up); go.SetLayerRecursive(GameObjectLayer); go.SetActive(true); return go; } private static void AdjustScaleAndCameraPosition(GameObject icon, Camera camera) { // get size of prefab var bounds = CalculateBounds(icon); float sphereDiameter = Mathf.Max(bounds.size.x, bounds.size.y, bounds.size.z); // rescale to size 1 unit so that object will take up as much viewspace as possible in ortho cam (with ortho size = 0.5) var currentScale = icon.transform.localScale; float scaleFactor = 1f / sphereDiameter; icon.transform.localScale = currentScale * scaleFactor; icon.transform.position = -bounds.center * scaleFactor; camera.transform.position = Vector3.zero; // back out, else we'll be inside the model (which is scaled at 1 unit so this should be plenty) camera.transform.Translate(new Vector3(0f, 0f, -5f), Space.Self); camera.transform.LookAt(Vector3.zero, Vector3.up); } public static Texture2D Create2D(AvailablePart part, int width, int height, Quaternion orientation, Color backgroundColor) { var cam = CreateCamera(width, height, backgroundColor); var icon = CreateIcon(part); var light = CreateLight(); var texture = new Texture2D(width, height, TextureFormat.ARGB32, false); var rt = RenderTexture.GetTemporary(width, height, 24); var prevRt = RenderTexture.active; RenderTexture.active = rt; icon.transform.rotation = orientation * icon.transform.rotation; AdjustScaleAndCameraPosition(icon, cam); cam.targetTexture = rt; cam.pixelRect = new Rect(0f, 0f, width, height); // doc says this should be ignored but doesn't seem to be (?) -- rendered area very small once targetTexture is set cam.Render(); texture.ReadPixels(new Rect(0f, 0f, width, height), 0, 0, false); texture.Apply(); RenderTexture.active = prevRt; RenderTexture.ReleaseTemporary(rt); Object.DestroyImmediate(light); Object.DestroyImmediate(cam); Object.DestroyImmediate(icon); return texture; } private static Bounds CalculateBounds(GameObject go) { var renderers = go.GetComponentsInChildren<Renderer>(true).ToList(); if (renderers.Count == 0) return default(Bounds); var boundsList = new List<Bounds>(); renderers.ForEach(r => { if (r.tag == IconHiddenTag) return; if (r is SkinnedMeshRenderer) { var smr = r as SkinnedMeshRenderer; // the localBounds of the SkinnedMeshRenderer are initially large enough // to accomodate all animation frames; they're likely to be far off for // parts that do a lot of animation-related movement (like solar panels expanding) // // We can get correct mesh bounds by baking the current animation into a mesh // note: vertex positions in baked mesh are relative to smr.transform; any scaling // is already baked in var mesh = new Mesh(); smr.BakeMesh(mesh); // while the mesh bounds will now be correct, they don't consider orientation at all. // If a long part is oriented along the wrong axis in world space, the bounds we'd get // here could be very wrong. We need to come up with essentially the renderer bounds: // a bounding box in world space that encompasses the mesh var m = Matrix4x4.TRS(smr.transform.position, smr.transform.rotation, Vector3.one /* remember scale already factored in!*/); var vertices = mesh.vertices; var smrBounds = new Bounds(m.MultiplyPoint3x4(vertices[0]), Vector3.zero); for (int i = 1; i < vertices.Length; ++i) smrBounds.Encapsulate(m.MultiplyPoint3x4(vertices[i])); Object.Destroy(mesh); boundsList.Add(smrBounds); } else if (r is MeshRenderer) // note: there are ParticleRenderers, LineRenderers, and TrailRenderers { r.gameObject.GetComponent<MeshFilter>().sharedMesh.RecalculateBounds(); boundsList.Add(r.bounds); } }); Bounds bounds = boundsList[0]; boundsList.Skip(1).ToList().ForEach(b => bounds.Encapsulate(b)); return bounds; } } Note that doing it this way (ReadPixels into a texture) is quite slow, not to mention the mesh baking it does if the part has an animation so call it as rarely as possible
-
What is sitMask, and how is it defined?
xEvilReeperx replied to linuxgurugamer's topic in KSP1 Mod Development
They're commented in ScienceDefs.cfg: // situation bits: // SrfLanded = 1, // SrfSplashed = 2, // FlyingLow = 4, // FlyingHigh = 8, // InSpaceLow = 16, // InSpaceHigh = 32 It's a simple bitmask. Add the numbers for the flags you want to set and you're good to go. Also applies to the biome mask Edit: It's the ExperimentSituations enum if you're doing something programmatically -
@JPLRepoI noticed that ProgressTree has a Deploy and Stow method which traverses the tree and runs some callbacks that hook/unhook each ProgressNode listener from their respective events. You could skip the ProgressNode (CelestialBodyScience) you're accidentally tripping by playing a little GameEvent shell game inside your contract using those callbacks... of course, it's a bit ugly // TSTTelescopeContract private void FakeCallback(float f, ScienceSubject s, ProtoVessel p, bool b) { GameEvents.OnScienceRecieved.Remove(FakeCallback); } protected override void AwardCompletion() { base.AwardCompletion(); var bodyName = GetParameter<TSTTelescopeContractParam>().target.name; var targetBody = FlightGlobals.Bodies.FirstOrDefault(cb => bodyName == cb.name); if (targetBody == null) { Debug.LogWarning("Couldn't find CelestialBody with name " + bodyName + " that " + typeof(TSTTelescopeContractParam).Name + " specifies"); return; } var subtree = ProgressTracking.Instance.GetBodyTree(targetBody); if (subtree.science.Subtree.Count > 0) { Debug.LogWarning("Multiple science subtree nodes for " + bodyName + " -- investigate"); return; } subtree.science.OnStow(); // removes it from onScienceReceived GameEvents.OnScienceRecieved.Add(FakeCallback); // we need a fake callback because the one that triggered this method call was removed by // TSTScienceParam.OnUnregister. GameEvent callbacks are stored in a list and called in reverse order // so the subtree.science callback we're about to add would otherwise be the next called subtree.science.OnDeploy(); // adds to end of onScienceReceived }
-
Objects derived from UnityEngine.Object overload Equals and will act like they're null after their unmanaged side has been destroyed. You'll note that m_shipRef isn't actually null -- you can still access, say, m_shipRef.altitude just fine. The moment you try and touch the (destroyed) Unity engine representation through something like m_shipRef.transform/rigidbody etc, you'll see an exception. [KSPAddon(KSPAddon.Startup.Instantly, true)] public class NullTestExample : MonoBehaviour { private void Start() { print("Hello, world! I am " + GetState()); DestroyImmediate(this); print("Now I'm " + GetState()); print("Let me just move myself now..."); transform.Translate(Vector3.up); } private string GetState() { return this == null ? "UNDEAD!!!" : "fine"; // note nonsensical comparison: surely this can never be NULL? } } [LOG 00:56:41.430] Hello, world! I am fine [LOG 00:56:41.431] Now I'm UNDEAD!!! [LOG 00:56:41.432] Let me just move myself now... [EXC 00:56:41.434] NullReferenceException UnityEngine.Component.get_transform () TestBed.NullTestExample.Start () (at d:/For New Computer/KSPCustomMods/TestBed/TestBed/NullTestExample.cs:18) Here's a link to the best explanation I could find
-
[1.1.3] FlagRotate - Adjust flag orientation
xEvilReeperx replied to xEvilReeperx's topic in KSP1 Mod Releases
Sure, looks like it didn't need any big changes. Updated