xEvilReeperx
Members-
Posts
894 -
Joined
-
Last visited
Content Type
Profiles
Forums
Developer Articles
KSP2 Release Notes
Everything posted by xEvilReeperx
-
Flip The Orientation The Part Will Appear In VAB
xEvilReeperx replied to dboi88's topic in KSP1 Mod Development
Oh, for some reason I read your post and thought "part icon" when you said selected. You should be able to create a new CFG using a MODEL{} node with the original model rotated. You'll have to edit the AttachNode locations manually but that should be straightforward. Here's a quick example of the mk1pod being flipped: [spoiler=ConfigNode][code]PART { name = mk1pod.upsidedown module = Part author = xEvilReeperx MODEL { model = Squad/Parts/Command/mk1pod/model rotation = 180, 0, 0 } // --- asset parameters --- scale = 1 rescaleFactor = 1 // --- node definitions --- // definition format is Position X, Position Y, Position Z, Up X, Up Y, Up Z node_stack_bottom = 0.0, -0.4050379, 0.0, 0.0, -1.0, 0.0, 1 node_stack_top = 0.0, 0.6423756, 0.0, 0.0, 1.0, 0.0, 0 bulkheadProfiles = size1, size0 CoPOffset = 0.0, 0.5, 0.0 CoLOffset = 0.0, -0.35, 0.0 CenterOfBuoyancy = 0.0, 0.5, 0.0 CenterOfDisplacement = 0.0, -0.3, 0.0 buoyancy = 1.5 buoyancyUseSine = False // --- editor parameters --- TechRequired = start entryCost = 0 cost = 600 category = Pods subcategory = 0 title = Look! It's upside down! manufacturer = Kerlington Model Rockets and Paper Products Inc description = Originally built as a placeholder for a demonstration mock-up of a rocket, the Mk1 Command Pod was heralded as a far safer and more reliable option than its predecessors by rocket scientists throughout the world. It is now commonly seen in active service. // attachment rules: stack, srfAttach, allowStack, allowSrfAttach, allowCollision attachRules = 1,0,1,1,0 // --- standard part parameters --- mass = 0.8 dragModelType = default maximum_drag = 0.2 minimum_drag = 0.15 angularDrag = 2 crashTolerance = 14 maxTemp = 1200 skinMaxTemp = 2200 skinInternalConductionMult = 0.625 heatConductivity = 0.1 // 5/6ths default vesselType = Ship // --- internal setup --- CrewCapacity = 1 INTERNAL { name = mk1PodCockpit } MODULE { name = ModuleCommand minimumCrew = 1 } RESOURCE { name = ElectricCharge amount = 50 maxAmount = 50 } MODULE { name = ModuleReactionWheel PitchTorque = 5 YawTorque = 5 RollTorque = 5 RESOURCE { name = ElectricCharge rate = 0.24 } } MODULE { name = ModuleScienceExperiment experimentID = crewReport experimentActionName = Crew Report resetActionName = Discard Crew Report reviewActionName = Review Report useStaging = False useActionGroups = True hideUIwhenUnavailable = True rerunnable = True xmitDataScalar = 1.0 usageReqMaskInternal = 5 usageReqMaskExternal = -1 } MODULE { name = ModuleScienceContainer reviewActionName = Review Stored Data storeActionName = Store Experiments evaOnlyStorage = True storageRange = 1.3 } RESOURCE { name = MonoPropellant amount = 10 maxAmount = 10 } MODULE { name = FlagDecal textureQuadName = flagTransform } MODULE { name = ModuleConductionMultiplier modifiedConductionFactor = 0.003 convectionFluxThreshold = 3000 } }[/code][/spoiler] -
The absolute minimum-effort method would be disabling the in-scene addon loading for Chatterer and loading a quicksave/switching scenes after reloading the plugin, now that the new version is out that fixed the problem with that before. Doing this should work for almost any mod with no other changes necessary. In the AppLauncher case, you could replace the event code with a coroutine that waits for ApplicationLauncher to be ready. I prefer that method(something like this:) [code]private IEnumerator Start() { while (ApplicationLauncher.Instance == null || !ApplicationLauncher.Ready) yield return 0; _button = ApplicationLauncher.Instance.AddModApplication(...); } private void OnDestroy() { if (_button != null && ApplicationLauncher.Instance != null) ApplicationLauncher.Instance.RemoveModApplication(_button); }[/code] You might find some other small issues, like Chatterer seems to assume an OnVesselChange event will be fired right after it starts up which won't be the case when reloading in-scene. That prevents some vessel-related stuff from being set
-
Flip The Orientation The Part Will Appear In VAB
xEvilReeperx replied to dboi88's topic in KSP1 Mod Development
If a plugin is an option, you can simply rotate the icon prefab (AvailablePart.iconPrefab) to point upside down -
Updated with a new version and some minor bugfixes, plus there is now an AppButton to toggle the main window. Don't be shy with feedback. You're not going to hurt my feelings if you tell me the documentation sucks or it's hard to use. It'd be nice to make this a staple in every modder's toolkit but I'll never get it there without some criticism :)
-
[quote name='Speadge']which pack was it? Have a similar problem - on starting a flight everything works well -but after some time / celestial body change, i cant transmit anymore.[/QUOTE] Are you getting the same exception as before? The change in RT might've been accidentally reverted somehow. [quote name='smjjames']I'm just letting you know that enabling SCANsat integration breaks it in that it doesn't sense new experiments. At least before I get the scansat stuff anyway.[/QUOTE] In hindsight, I implemented this terribly confusingly. If you enable SCANsat integration in SA, SA becomes blind to experiment subjects that are related to biome if you don't have that area of the planet mapped out. For instance, "EVA report in space" will trigger alerts but "EVA report while flying over shores" won't trigger at all if the area you're flying over hasn't been mapped for biome data by SCANsat. It sounds like it's working as intended. I'm busy rewriting big sections so I might as well ask how I might improve that? The 'is it working' question on SCANsat integration has come up a few times
-
Can't right-click
xEvilReeperx replied to Netskimmer's topic in KSP1 Technical Support (PC, modded installs)
Could also be an errant input lock. Next time it happens, open the debug menu (Alt+F12) and look under cheats menu at the input lock stack. If you see anything other than "DebugToolbar" (which appears while mouse is over the debug window itself) it might help track down the issue -
Still working great in 1.0.5, just needed a couple small additions and some changes to existing values to get the patcher working again MoveInitializerIntoAwake(asm, "AtmosphereFromGround", "\u0003", 8, "Start"); MoveInitializerIntoAwake(asm, "AtmosphereFromGround", "\u0004", 8, "Start"); MoveInitializerIntoAwake(asm, "AtmosphereFromGround", "\u0005", 8, "Start"); MoveInitializerIntoAwake(asm, "AtmosphereFromGround", "\u0006", 8, "Start"); MoveInitializerIntoAwake(asm, "AtmosphereFromGround", "\a", 8, "Start"); MoveInitializerIntoAwake(asm, "AtmosphereFromGround", "\b", 8, "Start"); MoveInitializerIntoAwake(asm, "AtmosphereFromGround", "\t", 8, "Start"); MoveInitializerIntoAwake(asm, "FlightIntegrator", "sunLayerMask", 24, "Start"); MoveInitializerIntoAwake(asm, "GameSettings", "INPUT_DEVICES", 2, "Awake"); MoveInitializerIntoAwake(asm, "HighLogic", "\u0019", 8, "Awake"); MoveInitializerIntoAwake(asm, "HighLogic", "\u001A", 8, "Awake"); MoveInitializerIntoAwake(asm, "HighLogic", "\u001B", 8, "Awake"); MoveInitializerIntoAwake(asm, "HighLogic", "\u001C", 8, "Awake"); MoveInitializerIntoAwake(asm, "HighLogic", "\u001D", 8, "Awake"); MoveInitializerIntoAwake(asm, "MapView", "\r", 8, "Start"); MoveInitializerIntoAwake(asm, "ModuleAblator", "\b\b", 8, "Start"); MoveInitializerIntoAwake(asm, "PhysicsGlobals", "\u001d\u0002", 8, "Awake"); MoveInitializerIntoAwake(asm, "PQSMod_MaterialQuadRelative", "\u001e\u0002", 8, "Awake"); MoveInitializerIntoAwake(asm, "PQSMod_MaterialQuadRelative", "\u001f\u0002", 8, "Awake"); MoveInitializerIntoAwake(asm, "PQSMod_MaterialQuadRelative", " \u0002", 8, "Awake"); MoveInitializerIntoAwake(asm, "PQSMod_OceanFX", "\n\u0003", 8, "Awake"); MoveInitializerIntoAwake(asm, "PQSMod_OceanFX", "\v\u0003", 8, "Awake"); MoveInitializerIntoAwake(asm, "PQSMod_OceanFX", "\f\u0003", 8, "Awake"); MoveInitializerIntoAwake(asm, "PQSMod_OceanFX", "\r\u0003", 8, "Awake"); MoveInitializerIntoAwake(asm, "SkySphereControl", "\u0002", 8, "Start"); MoveInitializerIntoAwake(asm, "SkySphereControl", "\u0003", 8, "Start"); MoveInitializerIntoAwake(asm, "SkySphereControl", "\u0004", 8, "Start"); MoveInitializerIntoAwake(asm, "SunShaderController", "\b", 8, "Start"); MoveInitializerIntoAwake(asm, "SunShaderController", "\t", 8, "Start"); MoveInitializerIntoAwake(asm, "SunShaderController", "\n", 8, "Start"); MoveInitializerIntoAwake(asm, "SunShaderController", "\v", 8, "Start"); MoveInitializerIntoAwake(asm, "UnderwaterTint", "colorID", 8, "Awake");
-
You didn't say how you were calling it, but I suspect that's the problem. It's meant to be used as a coroutine: private void DoSomethingWithScience(ScienceData data) { var myLab = // ModuleScienceLab here StartCoroutine(myLab.ProcessData(data, ScienceProcessingFinished)); // MonoBehaviour.StartCoroutine } private void ScienceProcessingFinished(ScienceData data) { PopupDialog.SpawnPopupDialog("Finished", "Science data has been processed", "OK", false, HighLogic.Skin); }
-
Will 1.0.5 fix Non-procedural fairings?
xEvilReeperx replied to almagnus1's topic in KSP1 Mods Discussions
[shameless]Try this[/shameless] -
In Chatterer's case, it creates its button when GameEvents.onGUIApplicationLauncherReady is fired. That event doesn't get fired mid-scene, so no button If you don't want to write custom code for that kind of situation, you could prevent AssemblyReloader from replacing things instantly in the options menu for the relevant plugin. In Chatterer's case, unchecking "Start Addons for current scene" and then switching scenes or reloading a quicksave works ... well, it would work if I didn't just find a facepalm programmer error which I am now fixing For something like EVE, again it should work -- it just might require some custom code to undo changes. I don't have any config or texture reloading in there currently because you can already reload the game database to achieve that. It's definitely possible but I don't have a use case for it at the moment
-
Editor and thumbnail questions
xEvilReeperx replied to linuxgurugamer's topic in KSP1 Mod Development
Only because he does not call the original method and instead overwrites what's already there. There are a number of ways around it. The easiest one is just to use a lower level callback directly: [KSPAddon(KSPAddon.Startup.EditorAny, false)] public class CraftHistoryButtonCallbackLowLevel : MonoBehaviour { private void Start() { EditorLogic.fetch.loadBtn.AddValueChangedDelegate(OnLoadButtonClicked); EditorLogic.fetch.saveBtn.AddValueChangedDelegate(OnSaveButtonClicked); } private void OnSaveButtonClicked(IUIObject uiObject) { print("Save button was clicked"); } private void OnLoadButtonClicked(IUIObject uiObject) { print("Load button was clicked"); } } Another is to do something similar to him, but store the details of the target method and call it yourself. I've built in a delay here so that his addon runs first (if installed), and then any changes he's made are captured for reuse: [KSPAddon(KSPAddon.Startup.EditorAny, false)] public class CraftHistoryButtonCallback : MonoBehaviour { private IEnumerator Start() { yield return new WaitForEndOfFrame(); gameObject.AddComponent<EditorHooks>().OnEditorSave += OnShipSaved; } private void OnShipSaved() { print("Callback for OnShipSaved received"); } } public class EditorHooks : MonoBehaviour { public event Callback OnEditorSave = delegate { }; private MonoBehaviour _originalSaveScript; private string _originalSaveMethodName; private void Awake() { var logic = EditorLogic.fetch; _originalSaveMethodName = logic.saveBtn.methodToInvoke; _originalSaveScript = logic.saveBtn.scriptWithMethodToInvoke; logic.saveBtn.scriptWithMethodToInvoke = this; logic.saveBtn.methodToInvoke = "HookedEditorSave"; } private void HookedEditorSave() { OnEditorSave(); // disabled because CraftHistory has overloaded "saveCraft" method and Unity complains //_originalSaveScript.SendMessage(_originalSaveMethodName, SendMessageOptions.RequireReceiver); // because we don't know whether the target method is potentially static, public etc ... we do know we're looking // for one that takes no arguments var methodInfo = _originalSaveScript.GetType() .GetMethod(_originalSaveMethodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] {}, null); if (methodInfo == null) { Debug.LogWarning("Did not find a method called " + _originalSaveMethodName + " on " + _originalSaveScript.GetType().FullName); } else { methodInfo.Invoke(_originalSaveScript, new object[] {}); } } } -
I had a sneaking suspicion which I confirmed by printing all events on the derived object and compared with the original PartModule. The names of the events themselves will give away the original method name (you can see how I don't even bother checking if "CustomLockSuspension" exists in the event list, I know it's there because I defined a method with that name). You can also use a decompiler which is probably the worst kept secret of modding. As for the SendMessage calls, that's a Unity thing. RequireReceiver is just used to prevent the message broadcast from silently failing in case a name is mistyped or changes in the future
-
You seem to pack (rails) and hold unpack in your TeleportToLocation method so it should work. private void DoTeleport() { var vessel = FlightGlobals.ActiveVessel; var originalUp = FlightGlobals.getUpAxis(); ActiveSeaLaunch.TeleportToLocation(89f, 0f, vessel); var newUp = FlightGlobals.getUpAxis(); // adjust vessel rotation based on how much the up direction changed var diff = Quaternion.FromToRotation(originalUp, newUp); vessel.SetRotation(diff * vessel.transform.rotation); } I don't know if you're using a different teleport method for this. You could write an equivalent method (of Vessel.SetRotation) that manipulates the part rigidbody locations as Boris suggested if packing the vessel isn't feasible for some reason
-
The problem is that your module is missing three Events that are apparently accessed inside ModuleLandingLeg.AnimationInitialState(): RepairLegs, LockSuspension and UnLockSuspension. They're associated with private methods that your class doesn't inherit. You can implement them yourself but you might have to reimplement any logic they have. I got around having to use reflection to access private stuff (bad) by renaming my custom events to impersonate the missing ones and then using abusing Unity's message broadcast to call the originals. It's pretty ugly but you can try it out if you'd like. Not well tested public class ModuleFixedLeg : ModuleLandingLeg { private bool _eventsInitialized = false; private KSPEvent GetCustomEventMethodInfo(string methodName) { return (KSPEvent)GetType() .GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .GetCustomAttributes(typeof(KSPEvent), true) .Single(); } private BaseEvent CreateEvent(string originalMethod, string customMethodName) { return new BaseEvent(Events, originalMethod, (BaseEventDelegate) Delegate.CreateDelegate(typeof (BaseEventDelegate), this, customMethodName), GetCustomEventMethodInfo(customMethodName)); } private void InitializeEvents() { if (_eventsInitialized) return; _eventsInitialized = true; Events.Remove(Events["CustomLockSuspension"]); Events.Remove(Events["CustomUnLockSuspension"]); Events.Add(CreateEvent("LockSuspension", "CustomLockSuspension")); Events.Add(CreateEvent("UnLockSuspension", "CustomUnLockSuspension")); } public override void OnInitialize() { InitializeEvents(); base.OnInitialize(); } public override void OnStart(StartState state) { InitializeEvents(); base.OnStart(state); } [KSPEvent(guiName = "Repair Leg", guiActiveUnfocused = true, externalToEVAOnly = true, guiActive = false, unfocusedRange = 4f)] private void RepairLeg() { // only jeb can repair these legs if (FlightGlobals.ActiveVessel.isEVA && FlightGlobals.ActiveVessel.parts.First().protoModuleCrew.Any(pcm => pcm.name.StartsWith("Jeb"))) { legState = LegStates.DEPLOYED; SendMessage("DecompressSuspension", SendMessageOptions.RequireReceiver); Events["TestBreak"].active = true; } else ScreenMessages.PostScreenMessage("Only Jeb can fix this", 5f); } [KSPEvent(guiName = "Lock Suspension", guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiActive = true, unfocusedRange = 4f)] private void CustomLockSuspension() { SendMessage("LockSuspension", SendMessageOptions.RequireReceiver); } [KSPEvent(guiName = "UnLock Suspension", guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiActive = true, unfocusedRange = 4f)] private void CustomUnLockSuspension() { SendMessage("UnLockSuspension", SendMessageOptions.RequireReceiver); } [KSPEvent(guiName = "Test Break", guiActiveEditor = false, guiActiveUnfocused = true, guiActive = true)] private void TestBreak() { SendMessage("BreakLeg", SendMessageOptions.RequireReceiver); Events["TestBreak"].active = false; } }
-
MM: Yep Code snippet: Yep also safe. I'm going to have to come up with a better explanation in the manual. Essentially I load the [reloadable] assemblies from memory which prevents them from having an actual Assembly.Location value and causes Assembly.CodeBase on them to point to the loader instead. Inside of your reloadable plugin, I can literally rewrite the call, so something like this: public class TestMethodInterception : MonoBehaviour { private void Awake() { print("CodeBase (executing assembly): " + Assembly.GetExecutingAssembly().CodeBase); print("CodeBase (typeof assembly): " + typeof (TestMethodInterception).Assembly.CodeBase); print("Location (executing assembly): " + Assembly.GetExecutingAssembly().Location); print("Location (typeof assembly): " + typeof (TestMethodInterception).Assembly.Location); } } Will be rewritten as (see YourPlugin.reloadable.patched inside your favorite disassembler): public class TestMethodInterception : MonoBehaviour { private void Awake() { MonoBehaviour.print("CodeBase (executing assembly): " + Helper.get_CodeBase(System.Reflection.Assembly.GetExecutingAssembly())); MonoBehaviour.print("CodeBase (typeof assembly): " + Helper.get_CodeBase(typeof(TestMethodInterception).Assembly)); MonoBehaviour.print("Location (executing assembly): " + Helper.get_Location(System.Reflection.Assembly.GetExecutingAssembly())); MonoBehaviour.print("Location (typeof assembly): " + Helper.get_Location(typeof(TestMethodInterception).Assembly)); } } With that helper method being injected as: internal class Helper { public static string get_CodeBase(System.Reflection.Assembly assembly) { if (object.ReferenceEquals(System.Reflection.Assembly.GetExecutingAssembly(), assembly)) { return "file:///[...]/Kerbal Space Program/GameData/TestMethodInterception/TestMethodInterception.reloadable"; } return assembly.CodeBase; } public static string get_Location(System.Reflection.Assembly assembly) { if (object.ReferenceEquals(System.Reflection.Assembly.GetExecutingAssembly(), assembly)) { return "[...]\\Kerbal Space Program\\GameData\\TestMethodInterception\\TestMethodInterception.reloadable"; } return assembly.Location; } } But for other plugins that have already been loaded, it's already too late and nothing I can do. That's why MM stalls out, an empty Assembly.Location is not a path of legal form
-
Cool, thanks for trying it out. The issue with ModuleManager is mentioned briefly in the manual but basically it's trying to get a file path of an assembly and because of the way I load them, the field is blank. It's easily fixed inside the MM code though (bolded): foreach (AssemblyLoader.LoadedAssembly mod in AssemblyLoader.loadedAssemblies) { try { [B] if (string.IsNullOrEmpty(mod.assembly.Location)) continue;[/B] FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(mod.assembly.Location); AssemblyName assemblyName = mod.assembly.GetName(); [snip] Accessing that same field inside of your reloaded assembly (usually via Assembly.GetExecutingAssembly().Location/CodeBase) is fine since it will be replaced with a call to a method that returns the correct thing, assuming you have the relevant option enabled. The other issue was a facepalm oversight of mine and is now fixed in 1.0.1
-
Yes, all of the types local to a version of the assembly are unique to it and will be "reloaded" in the sense that from their perspective, they're being created for the very first time. The types that need special code from AssemblyReloader are the ones that KSP "magically" finds. I put the restriction in place because it would otherwise be possible to have multiple versions of an assembly being actively used leading to a great deal of confusion. You'll be responsible for any cleanup though. Example: public class MySettingsSingleton { private static MySettingsSingleton _instance; public static MySettingsSingleton Instance { get { if (_instance != null) return _instance; _instance = new MySettingsSingleton(); GameEvents.onGameStateSave.Add(_instance.OnGameSave); GameEvents.onGameStateLoad.Add(_instance.OnGameLoad); return _instance; } } private void OnGameSave(ConfigNode config) { // ... } private void OnGameLoad(ConfigNode config) { // ... } } When the plugin containing this type is reloaded, this particular object will stick around due to its static nature. When the "new" version refers to a MySettingsSingleton, it will refer to its own version which hasn't been initialized and suddenly you've now got two of them hanging around. This example is pretty harmless because those GameEvent registrations will be forcefully removed on reload making it inert (and a warning will be displayed in the log letting you know you've forgotten to unregister something) but you can see how there might be unintended consequences depending on what exactly this object does