Jump to content

Trying to replace ModuleLandingLeg


Recommended Posts

Hi! Basically I'm trying to replace the stock ModuleLandingLeg to be my "ModuleFixedLeg" with everything exactly the same, except that I replace the repair functionality with my own (where I can change the engineer level required to fix it).

Basically, to start off I made

public class ModuleFixedLeg: ModuleLandingLeg
{

}

and in my module manager config, I have

@PART[*]:HAS[@MODULE[ModuleLandingLeg]]:AFTER[EngineerLevelFixer]
{
@MODULE[ModuleLandingLeg]
{
@name=ModuleFixedLeg
}
}

So when I load the code, I go into the VAB, and try to select a Landing Leg, I get the following error in the KSP.log

[EXC 20:42:18.764] NullReferenceException: Object reference not set to an instance of an object
ModuleLandingLeg.AnimationInitialState ()
ModuleLandingLeg.InitialSetup ()
ModuleLandingLeg.OnInitialize ()
Part.InitializeModules ()
EditorLogic.SpawnPart (.AvailablePart partInfo)
EditorLogic.OnPartListIconTap (.AvailablePart p)
EditorPartList.TapIcon (.AvailablePart part)
EditorPartIcon.OnTap ()
EditorPartIcon.MouseInput (.POINTER_INFO& ptr)
UIButton.OnInput (.POINTER_INFO& ptr)
AutoSpriteControlBase.OnInput (POINTER_INFO ptr)
UIManager.DispatchHelper (.POINTER_INFO& curPtr, Int32 camIndex)
UIManager.DispatchInput ()
UIManager.Update ()
UIManager.DidAnyPointerHitUI ()
VABCamera.Update ()

And that's it... I didn't change anything else. This method worked fine when I tried it with wheels. What do I need to change to make my code work/where can I look for an example of this working properly?

Link to comment
Share on other sites

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;
}
}

Link to comment
Share on other sites

-snip-

Thanks so much! After a bit of messing around, I got your messing about I got my code to work properly. I don't really understand the reflection very well, so I just copied your first two methods wholesale. They do work fine though :)

I discovered that I couldn't just have an object field for "hasInitialized" because apparently the object is copied on symmetry (or something?) because I got NREs when I used symmetry on the parts. And I couldn't not check because the game added the fields twice more on ship launch. Instead I just checked if the field was null before adding it.

Then I used my own logic for the repair leg :D

I had a couple of questions though. First, how did you know what methods I needed to implement? Those methods weren't in the generated declaration in Visual Studio, nor were they in the error message I got originally.

Second, how do you know what messages to call in "SendMessage"? Is there a list somewhere? Also along with this , what does RequireReceiver/DontRequireReceiver do in the SendMessage?

Anyways, thanks for all the help!

Link to comment
Share on other sites

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

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...