• Content Count

  • Joined

  • Last visited

Community Reputation

1,083 Excellent


About xEvilReeperx

  • Rank
    Junior Rocket Scientist

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. It doesn't matter for the Canvas' children, but it does for the Canvas itself. Also, the Canvas appears to copy the camera's transform and rotation which means the position and orientation of the Image you're adding are relevant. Here's a fixed version: private void Start() { GameObject canvasGO = new GameObject("censorcanvas"); canvasGO.layer = 0; // must be in camera's culling mask canvasGO.AddComponent<RectTransform>(); canvas = canvasGO.AddComponent<Canvas>(); canvas.renderMode = RenderMode.ScreenSpaceCamera; // UI elements do not show up if we use the local space camera Camera flightCam = FlightCamera.fetch.mainCamera; canvas.worldCamera = flightCam; canvas.planeDistance = flightCam.nearClipPlane + 0.5f; canvas.pixelPerfect = true; CanvasScaler canvasScalar = canvasGO.AddComponent<CanvasScaler>(); canvasScalar.uiScaleMode = CanvasScaler.ScaleMode.ConstantPixelSize; canvasGO.AddComponent<GraphicRaycaster>(); GameObject panelGO = new GameObject("censorbar"); panelGO.layer = LayerMask.NameToLayer("UI"); panel = panelGO.AddComponent<RectTransform>(); panelGO.transform.SetParent(canvasGO.transform, false); // gets moved Image censorbar = panelGO.AddComponent<Image>(); Texture2D tex = GameDatabase.Instance.GetTexture("Harm/harm", false); censorbar.sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f)); panel.anchorMin = new Vector2(0, 0); panel.anchorMax = new Vector2(0, 0); panel.pivot = new Vector2(0.5f, 0.5f); panel.sizeDelta = new Vector2(tex.width, tex.height); } private void LateUpdate() { // this is the nearer of the two local space cameras Camera cam = FlightCamera.fetch.mainCamera; // make image track kerbal position on screen Vector2 screenPoint = (Vector2)cam.WorldToScreenPoint(transform.position); panel.anchoredPosition = screenPoint; // eventual goal is to also put image just in front of kerbal so it gets occluded by other closer objects // canvas.planeDistance = Vector3.Dot((transform.position - cam.transform.position), cam.transform.forward) - 0.5; canvas.planeDistance = Vector3.Distance(transform.position, cam.transform.position); }
  2. But why? Here's what I meant: private class JellyListener : MonoBehaviour { private JellyfishDishMk2 _jellydish; // your PartModule here private void Awake() { _jellydish = GetComponentInParent<JellyfishDishMk2>(); if (_jellydish == null) Debug.LogError("Didn't find expected Jellyfish PM"); } private void FirstAnimationFrame(UnityEngine.Object param) { var pm = param as JellyfishDishMk2; if (pm != null) pm.PlayStartingSound(); } // alternative method: note the lack of argument private void LastAnimationFrame() { _jellydish.PlayEndingSound(); } } // add events at beginning and end jellyfish.clip.AddEvent(new AnimationEvent() { time = 0f, functionName = "FirstAnimationFrame", objectReferenceParameter = this // note that any UnityEngine.Object works here: we could pass a sound, texture, ScriptableObject, MonoBehaviour, ... }); jellyfish.clip.AddEvent(new AnimationEvent() { time = jellyfish.length, functionName = "LastAnimationFrame" // note didn't use objectRef here, see listener method });
  3. While you could do that (any UnityEngine.Object or derived class ... like PartModule or MonoBehaviour), you don't need to. You already know exactly where your data is. You could cache the PartModule or wherever your data is being kept in whatever script you have as an event listener and call methods directly
  4. This kind of error can be a real PITA to debug, but in this case it's correct. The specific problem (at least, in my test bench of just downloading KAS 1.2 + current version of KOS) is that SaveUpgradePipeline.KAS.PatchFilesProcessor fails to load. This happens because it doesn't implement an abstract method. Between 1.6 and 1.7, UpgradeScript.OnUpgrade's signature changed. I didn't check that other mod you mentioned but I imagine something similar is going on. I don't think there's a 1.7 version of KAS yet or else this would be caught already
  5. Are you 100% sure it exported successfully? That looks like it might be the result of an incomplete model. Delete, re-export, then examine Unity console for any exceptions.
  6. You don't need ref or out here. Just use isDragging directly in the lambda functions; C# will create a closure over it and you effectively have a reference to it (although secretly the compiler creates a private inner class which is how it pulls this off). By the way, you probably don't want to be inserting triggers in OnUpdate(): every time the slider value changes, you add another set of triggers
  7. Something sounds funny in your explanation. If you want everything to stay in one function, what are you outputting to? Why does the float need to be passed by reference? Changing it won't make any difference, you'll want to call a method on the slider itself to change it anyways or you might break state stuff. Assuming "which" slider is the problem (and you REALLY wanted to keep it all in one function [why?]), you could always do something like this: private enum WhichSlider // or however you want to identify it, right down to calling different functions { First, Second, Third } // embed which slider is clicked into another anon function private void Foo() { DialogGUISlider slider1; // = whatever DialogGUISlider slider2; DialogGUISlider slider3; // can make an inner func for logic... Action<float, WhichSlider> lambdaSliderChanged = (f, which) => { print("Slider " + which + " is now " + f); }; slider1.slider.onValueChanged.AddListener(val => { lambdaSliderChanged(val, WhichSlider.First); }); slider2.slider.onValueChanged.AddListener(val => { lambdaSliderChanged(val, WhichSlider.Second); }); slider3.slider.onValueChanged.AddListener(val => { lambdaSliderChanged(val, WhichSlider.Third); }); }
  8. Can you be more specific about what you're trying to do? There isn't any code related to the part's appearance itself before PartModules start getting loaded. The part prefab is instantiated as is and then PartModules do the thing, one of which may be to modify the part's appearance. Why can't you modify the part inside OnLoad? I'm fairly sure that fires in the same frame as when the part itself is created, so changing part appearance there is fine and the user will never notice
  9. The structure is destroyed because you're not really saving the original contents of the file your ConfigNode comes from. ConfigNodes loaded by the game are all wrapped in a root ConfigNode that looks like this: root { KERBALCHANGELOG { ... } } This is so you can have multiple "inner nodes" in one cfg. You want to save the file itself to keep this structure, using cfgDir.parent.SaveFile()
  10. AudioClip clip = GameDatabase.Instance.GetAudioClip(gdb_path); clip.name = name; // <----- bad plan planetMap.Add(name, clip); fileNames.Add(clip, name); I haven't tried running your code but at a glance this stands out. Usually the GameDatabase names are the GameData-relative URLs, so on your first run this clip will likely be named something like SoundsOfSpace/Sounds/SoundsOfSpace/yourfile After successfully finding this clip the first time, you inexplicably rename it to yourfile.ogg. This will be a problem because GameDatabase.Instance.ExistsAudioClip is checking the clip names for a match
  11. This is precisely why I won't incorporate the work of others into my own stuff: a large number of these licenses have either been poisoned by unwritten rules or do not reflect the actual intent of the owner who has copy-pasted it from somewhere, and so they might as well be All Rights Reserved. Secret, unwritten rules are not transparency. If I look at a license that says YOU MAY DO ANYTHING WITH THIS WORK and think, gee, I'll have to make sure that's true, then the license is incomplete at best and useless at worst.
  12. You've nearly got it. Your issue is that your window rect is in screen space (0,0 = top left) while ReadPixels (and CopyTexture) expects pixel space (0,0 = bottom left) Convert to pixel space by inverting the y coordinate (Screen.height - y) and subtracting out the window height to get the bottom-left corner of the window [KSPAddon(KSPAddon.Startup.MainMenu, false)] class CaptureWindowScreenshot : MonoBehaviour { private Rect _windowRect = new Rect(Screen.width * 0.5f, Screen.height * 0.5f - 200, 200f, 50f); private readonly GUILayoutOption[] _defaultOptions = new GUILayoutOption[0]; private GUI.WindowFunction _windowFunc; private bool _capture = false; private void Awake() { _windowFunc = DrawWindow; } private void OnGUI() { _windowRect = KSPUtil.ClampRectToScreen(GUILayout.Window(GetInstanceID(), _windowRect, _windowFunc, "Test Window", _defaultOptions)); if (_capture && Event.current.type == EventType.Repaint) { _capture = false; StartCoroutine(Capture()); } } private IEnumerator Capture() { yield return new WaitForEndOfFrame(); var pixelRect = _windowRect; // copy values pixelRect.y = Mathf.Max(Screen.height - _windowRect.y - _windowRect.height, 0f); var captureTex = new Texture2D(Mathf.CeilToInt(pixelRect.width), Mathf.CeilToInt(pixelRect.height)); captureTex.ReadPixels(pixelRect, 0, 0, false); captureTex.SaveToDisk("window.png"); // your preferred method here Destroy(captureTex); } private void DrawWindow(int winid) { for (int i = 0; i < 5; ++i) GUILayout.Label("Text goes here", _defaultOptions); GUILayout.Space(5f); if (GUILayout.Button("Capture this window", _defaultOptions)) _capture = true; GUI.DragWindow(); } }
  13. From easiest to most convoluted: Cheat by implementing a similarly-named KSPEvent (with different method name) and abusing GameObject.SendMessage Reimplement the method yourself (VesselRenameDialog looks interesting) Fix the cached reflected delegate data yourself. This will make things "just work", but it's a little hacky [KSPAddon(KSPAddon.Startup.MainMenu, true)] class FixSetVesselNamingIssue : MonoBehaviour { class SneakyWayIntoCachedReflectedData : BaseEventList { public SneakyWayIntoCachedReflectedData(Type nobodyCares) : base(nobodyCares) { EnsureDataExists<Part>(); // if ALL modules changed, might not be any cached data for Part EnsureDataExists<PartTapIn>(); // it's possible some MM failure didn't edit the parts as expected, so confirm here too // "reflectedAttributeCache" is part of BaseEventList and is why we derive from it -> to get access so // we can tweak it var partData = reflectedAttributeCache[typeof(Part)]; // <-- this has an event we want to copy var tapinData = reflectedAttributeCache[typeof(PartTapIn)]; // <-- this is where we'll copy it to // unfortunately the only way to tell the various attributes apart // is to examine their guiName, which is localized #autoLOC_8003140 var targetLocalizedName = Localizer.Format("#autoLOC_8003140"); var configureNamingAttr = partData.eventAttributes.Single(kspe => kspe.guiName.Equals(targetLocalizedName, StringComparison.Ordinal)); if (tapinData.eventAttributes.Any( kspe => kspe.guiName.Equals(targetLocalizedName, StringComparison.Ordinal))) { return; // clearly changes already made } // insert attribute into PartTapIn data tapinData.eventAttributes.Add(configureNamingAttr); // copy "SetVesselNaming" delegate from Part to PartTapIn // this'll work because it's technically part of PartTapIn as well // note: method name directly, no localization needed here var setVesselNamingFunc = partData.eventDelegates.Single(mi => "SetVesselNaming".Equals(mi.Name)); tapinData.eventDelegates.Add(setVesselNamingFunc); } private static void EnsureDataExists<T>() where T : MonoBehaviour { if (reflectedAttributeCache.ContainsKey(typeof(T))) return; // already cached // if we create an inactive GameObject, we can prevent stuff from triggering // (awake, start) var dummyGo = new GameObject("dummy"); dummyGo.SetActive(false); // suppress awake, start var target = dummyGo.AddComponent<T>(); var dummy = new BaseEventList(target); // force data to be cached UnityEngine.Object.Destroy(dummyGo.gameObject); } } private void Start() { try { new SneakyWayIntoCachedReflectedData( typeof(Part)); // we literally don't care about anything but getting this constructor to run } catch (Exception e) { Debug.LogError("Exception while trying to meddle with cached data: " + e); } finally { Destroy(gameObject); // done } } }
  14. The exception in the log was the first clue. I looked at the code and didn't see any reason why that would prevent a simple print statement made in Update from working, so I wrote another small addon that checked out KeyNode's state. There were three possibilities as to why Update wasn't running: the component had been destroyed due to the uncaught exception in Awake. I've never seen this before, but thought it could be something AddonLoader does on an unhandled exception the GameObject wasn't active the MonoBehaviour was disabled (by Unity?) Turns out #3 was your problem
  15. It throws an uncaught exception in Awake and its MonoBehaviour ends up getting disabled (MonoBehaviour.enabled = false) when initializing for the second+ time. That's why you don't see any output from Update on subsequent flight scenes ArgumentException: An element with the same key already exists in the dictionary. System.Collections.Generic.Dictionary`2[UnityEngine.KeyCode,Callback].Add (KeyCode key, .Callback value) KeyNode.KeyNode.Awake () UnityEngine.GameObject:AddComponent(Type) AddonLoader:StartAddon(LoadedAssembly, Type, KSPAddon, Startup) AddonLoader:StartAddons(Startup) AddonLoader:OnLevelLoaded(GameScenes) AddonLoader:OnSceneLoaded(Scene, LoadSceneMode) UnityEngine.SceneManagement.SceneManager:Internal_SceneLoaded(Scene, LoadSceneMode)