Jump to content

Working with crew capacity changes by ModuleAnimateGeneric (InflatableAirlock)


Recommended Posts

(Updated May 14 with additional gotchas)

The InflatableAirlock in Making History has dynamic crew capacity, implemented using the new ModuleAnimateGeneric.CrewCapacity field
To detect when the crew capacity has changed, you can register for ModuleAnimateGeneric.OnStop ; however, there are a couple of quirks/caveats to be aware of.

- * - * - * - * - * -

First, there is a bit of a quirk in the response when in editor, as opposed to flight.

To demonstrate, here's a PartModule that cares if the part it is attached to has dynamic crew capacity

public class ModuleAnimateGenericNotify : PartModule
{
	public override void OnStart(StartState state) {
		IEnumerator<ModuleAnimateGeneric> mags = part.FindModulesImplementing<ModuleAnimateGeneric>().Where(mag => mag.CrewCapacity > 0).GetEnumerator();
		while (mags.MoveNext()) {
			mags.Current.OnMoving.Add(OnMovingHandler);
			mags.Current.OnStop.Add(OnStopHandler);
		}
		mags.Dispose();
	}
	private void OnDestroy() {
		IEnumerator<ModuleAnimateGeneric> mags = part.FindModulesImplementing<ModuleAnimateGeneric>().Where(mag => mag.CrewCapacity > 0).GetEnumerator();
		while (mags.MoveNext()) {
			mags.Current.OnMoving.Remove(OnMovingHandler);
			mags.Current.OnStop.Remove(OnStopHandler);
		}
		mags.Dispose();
	}
	private void OnStopHandler (float param) {
		Debug.Log($"OnStopHandler({param}) triggered for part {part.partInfo.name}; capacity: {part.CrewCapacity}.");
	}
	private void OnMovingHandler (float param1, float param2) {
		Debug.Log($"OnMovingHandler({param1},{param2}) triggered for part {part.partInfo.name}; capacity: {part.CrewCapacity}.");
	}
}

In flight mode, it behaves as expected:

// FLIGHT, Open Airlock
[LOG 12:13:37.289] OnMovingHandler(0,1) triggered for part InflatableAirlock; capacity: 0.
[LOG 12:13:39.686] OnStopHandler(1) triggered for part InflatableAirlock; capacity: 1.

// FLIGHT, Close Airlock
[LOG 12:13:41.435] OnMovingHandler(1,0) triggered for part InflatableAirlock; capacity: 1.
[LOG 12:13:43.930] OnStopHandler(0) triggered for part InflatableAirlock; capacity: 0.

But in the editor, the crew capacity isn't updated yet when OnStop occurs for airlock opening:

// EDITOR, Open Airlock
[LOG 12:12:59.081] OnMovingHandler(0,1) triggered for part InflatableAirlock; capacity: 0.
[LOG 12:12:59.330] OnStopHandler(1) triggered for part InflatableAirlock; capacity: 0.	// welp!

// EDITOR, Close Airlock
[LOG 12:13:01.168] OnMovingHandler(1,0) triggered for part InflatableAirlock; capacity: 1.
[LOG 12:13:01.502] OnStopHandler(0) triggered for part InflatableAirlock; capacity: 0.

To get the correct behavior, wait until the end of the frame before checking capacity:

	private void OnStopHandler (float param) {
		Debug.Log($"OnStopHandler({param}) triggered for part {part.partInfo.name}; capacity: {part.CrewCapacity}.");
		if (HighLogic.LoadedSceneIsEditor)
			StartCoroutine(EditorOnStopDelay());
	}
	private IEnumerator EditorOnStopDelay() {
		yield return new WaitForEndOfFrame();
		Debug.Log($"EditorOnStopDelay for part {part.partInfo.name}; capacity: {part.CrewCapacity}.");	// this will report the correct capacity
	}

Not sure if this counts as a bug or if there is some rational reason for it to behave like this.
In any case, the tiny delay in reporting capacity change from zero to non-zero is not likely to cause any serious logical errors.

- * - * - * - * - * -

The second gotcha is a bit more serious -- it could lead to buggy behavior on rare occasions.

If we expand our code to actually monitor the crew capacity throughout the entire animation process:

private void OnMovingHandler(float start, float end) {
	Debug.Log("DEBUG: OnMovingHandler("+start+","+end+") progress = "+ mag.Progress + "; crew capacity = " + part.CrewCapacity);
	monitor = true;
}

private void OnStopHandler(float progress) {
	Debug.Log("DEBUG: OnStopHandler("+progress+") progress = "+ mag.Progress + "; crew capacity = " + part.CrewCapacity);
	monitor = false;
}

public override void OnUpdate() {
	if (monitor)
		Debug.Log("DEBUG: OnUpdate() crew capacity = " + part.CrewCapacity);
}

Here is what the log output (in FLIGHT) looks like:

// Deploying

[LOG 13:08:27.833] DEBUG: OnMovingHandler(0,1) progress = 0; crew capacity = 0
[LOG 13:08:27.834] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:27.921] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.014] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.100] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.197] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.288] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.381] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.480] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.565] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.655] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.749] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.835] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:28.931] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.022] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.116] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.200] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.299] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.390] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.485] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.583] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.668] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.773] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.853] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:29.974] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:30.039] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:30.159] DEBUG: OnUpdate() crew capacity = 1
[LOG 13:08:30.225] DEBUG: OnStopHandler(1) progress = 1; crew capacity = 1


// Retracting

[LOG 13:08:50.368] DEBUG: OnMovingHandler(1,0) progress = 1; crew capacity = 1
[LOG 13:08:50.369] DEBUG: OnUpdate() crew capacity = 1				// !!!
[LOG 13:08:50.462] DEBUG: OnUpdate() crew capacity = 1				// !!!
[LOG 13:08:50.565] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:50.646] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:50.736] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:50.820] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:50.914] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.001] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.174] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.290] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.305] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.381] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.471] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.553] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.649] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.734] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.838] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:51.918] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:52.016] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:52.100] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:52.191] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:52.288] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:52.384] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:52.484] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:52.574] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:52.656] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:52.752] DEBUG: OnUpdate() crew capacity = 0
[LOG 13:08:52.833] DEBUG: OnStopHandler(0) progress = 0; crew capacity = 0

Notice that when retracting the airlock, the crew capacity can remain at >0 for a couple of OnUpdate() cycles after the retract animation has started playing.

If we consider plugin mods with functionality that moves Kerbals into / between parts, it is common to test for space available using:

if (part.protoModuleCrew.Count < part.CrewCapacity)
  ...

There is thus a very small possibility that, if this test occurs within those couple of frames, the mod will erroneously consider the inflatable airlock to have an open seat available.
If the mod then moves a Kerbal into the part (while crew capacity > 0), it will actually succeed:

private void OnMovingHandler(float start, float end) {
	...
	if (start == 1f)
		StartCoroutine(MoveKerbal());
}

private IEnumerator MoveKerbal() {
	yield return 0;							// wait one frame
	ProtoCrewMember victim = part.vessel.GetVesselCrew()[0];	// grab an arbitrary kerbal in the vessel
	Debug.Log("DEBUG: " + victim.name + " has been \"volunteered\" to enter the retracting airlock...");
	Part fromPart = victim.KerbalRef.InPart;
	fromPart.RemoveCrewmember(victim);
	part.AddCrewmember(victim);
	Debug.Log("DEBUG: " + victim.name + " moved from " + fromPart.name + " to " + part.name);
}

Although KSP will check for available crew capacity when part.AddCrewmember(victim); is called, the test will erroneously pass.

[LOG 15:15:45.120] DEBUG: OnMovingHandler(1,0) progress = 1; crew capacity = 1
[LOG 15:15:45.121] DEBUG: OnUpdate() crew capacity = 1
[LOG 15:15:45.212] DEBUG: OnUpdate() crew capacity = 1
[LOG 15:15:45.213] DEBUG: Bill Kerman has been "volunteered" to enter the retracting airlock...
[LOG 15:15:45.213] DEBUG: Bill Kerman moved from mk1-3pod to InflatableAirlock
[LOG 15:15:45.325] DEBUG: OnUpdate() crew capacity = 0
[LOG 15:15:45.413] DEBUG: OnUpdate() crew capacity = 0
...

This leads to a kerbal sitting inside the retracted airlock, which then has crew capacity = 0 and crew count = 1.
Due to the presence of crew in the part, it cannot be deployed -- animation is disabled thanks to a check that was intended to prevent retraction of an occupied airlock.

 

Edited by cakepie
Update findings -- more gotchas
Link to comment
Share on other sites

  • 2 weeks later...
On 4/18/2018 at 10:26 PM, cakepie said:

The InflatableAirlock in Making History has dynamic crew capacity, implemented using the new ModuleAnimateGeneric.CrewCapacity field
To detect when the crew capacity has changed, you can register for ModuleAnimateGeneric.OnStop ; however, there is a bit of a quirk when in editor (as opposed to flight)

 

To demonstrate, here's a PartModule that cares if the part it is attached to has dynamic crew capacity


public class ModuleAnimateGenericNotify : PartModule
{
	public override void OnStart(StartState state) {
		IEnumerator<ModuleAnimateGeneric> mags = part.FindModulesImplementing<ModuleAnimateGeneric>().Where(mag => mag.CrewCapacity > 0).GetEnumerator();
		while (mags.MoveNext()) {
			mags.Current.OnMoving.Add(OnMovingHandler);
			mags.Current.OnStop.Add(OnStopHandler);
		}
		mags.Dispose();
	}
	private void OnDestroy() {
		IEnumerator<ModuleAnimateGeneric> mags = part.FindModulesImplementing<ModuleAnimateGeneric>().Where(mag => mag.CrewCapacity > 0).GetEnumerator();
		while (mags.MoveNext()) {
			mags.Current.OnMoving.Remove(OnMovingHandler);
			mags.Current.OnStop.Remove(OnStopHandler);
		}
		mags.Dispose();
	}
	private void OnStopHandler (float param) {
		Debug.Log($"OnStopHandler({param}) triggered for part {part.partInfo.name}; capacity: {part.CrewCapacity}.");
	}
	private void OnMovingHandler (float param1, float param2) {
		Debug.Log($"OnMovingHandler({param1},{param2}) triggered for part {part.partInfo.name}; capacity: {part.CrewCapacity}.");
	}
}

 

I use "GameEvents.OnAnimationGroupStateChanged.Add(OnModuleAnimationGroupStateChanged);" to detect animations based on ModuleAnimationGroup.

This helped me trigger when the animation was finished.

Also see https://github.com/WarezCrawler/Guybrush101/blob/master/GTI_Utilities/BaseClass/MultiMode.cs#L456-L507

 

EDIT: Sorry.... This will not help you.... I did not notice it is not the same module..... ModuleAnimateGeneric does not have that event.

EDIT2: Have you checked the animation state? "animationStates" --> LOCKED, MOVING, CLAMPED, FIXED

Edited by Warezcrawler
Link to comment
Share on other sites

  • 2 weeks later...

Added new findings to the OP, related to crew transfers into the inflatable airlock.

tl;dr:
The inflatable airlock's Part.CrewCapacity remains nonzero for a couple of frames after the airlock starts retracting.
It is possible to successfully perform a crew transfer into the inflatable airlock within these couple of frames, while the part is animating.
This will lead to buggy behavior subsequently.

Link to comment
Share on other sites

6 hours ago, cakepie said:

It is possible to successfully perform a crew transfer into the inflatable airlock within these couple of frames, while the part is animating.
This will lead to buggy behavior subsequently.

If you know you are interfacing with an animated part, it should be a simple enough check to see if the part is mid-animation before doing any crew manipulations.

 

Quick question to anyone who has used dynamic crew capacity -- does it update the number of available kerbal-seats properly in the editor?  In previous KSP versions it used to be... problematic at the least, and updating the seats in the editor was nearly impossible to do without issues.

Link to comment
Share on other sites

9 minutes ago, Shadowmage said:

If you know you are interfacing with an animated part, it should be a simple enough check to see if the part is mid-animation before doing any crew manipulations.

You're exactly right, that is the necessary workaround to avoid this gotcha.
But it feels rather like we're compensating for shortcomings in the stock implementation.

(To be precise: not all animated parts, just specifically those ModuleAnimateGeneric modules that alter CrewCapacity.)

 

30 minutes ago, Shadowmage said:

Quick question to anyone who has used dynamic crew capacity -- does it update the number of available kerbal-seats properly in the editor?

Programmatic access to Part.CrewCapacity is functional, with minor caveat as described in OP.

Are you referring the the "crew" tab {build, actions, crew} for seating?
Nope, that doesn't update automatically to reflect extra seat added/removed when deploying/retracting the Making History inflatable airlock.

 

Link to comment
Share on other sites

14 minutes ago, cakepie said:

But it feels rather like we're compensating for shortcomings in the stock implementation.

Aren't we always :)

14 minutes ago, cakepie said:

Nope, that doesn't update automatically to reflect extra seat added/removed when deploying/retracting the Making History inflatable airlock.

^^^ Yep, was what I was referring to.   This was always the part that I had trouble getting to work before.  Sure, you could adjust the crew capacity of the part.... but it never worked properly in the editor.


For once I wish stock would actually implement a feature in full.  Something such as this really should work, exactly as expected, in all situations (e.g. in the editor...).  Shouldn't be that hard to refresh the editor crew-list UI on crew capacity changes.... (I don't know KSP source, so it might actually be difficult... but it still shouldn't be).

 

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...